blob: 7a3bbaf33539a7747d2fd0af19d72427e3a7978e [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[] = {
42 "ACL",
43 "API",
44 "ARGB",
45 "ASCII",
46 "BGRA",
47 "CMYK",
48 "DNS",
49 "FPS",
50 "FTP",
51 "GIF",
52 "GPS",
Yan Zhangee630392018-02-27 18:35:53 +000053 "GUID",
Ben Hamiltonf94c10d2018-01-22 15:45:25 +000054 "HD",
55 "HDR",
56 "HTML",
57 "HTTP",
58 "HTTPS",
59 "HUD",
60 "ID",
61 "JPG",
62 "JS",
63 "LAN",
64 "LZW",
65 "MDNS",
66 "MIDI",
67 "OS",
68 "PDF",
69 "PIN",
70 "PNG",
71 "POI",
72 "PSTN",
73 "PTR",
74 "QA",
75 "QOS",
76 "RGB",
77 "RGBA",
78 "RGBX",
79 "ROM",
80 "RPC",
81 "RTF",
82 "RTL",
83 "SDK",
84 "SSO",
85 "TCP",
86 "TIFF",
87 "TTS",
88 "UI",
89 "URI",
90 "URL",
Yan Zhangee630392018-02-27 18:35:53 +000091 "UUID",
Ben Hamiltonf94c10d2018-01-22 15:45:25 +000092 "VC",
93 "VOIP",
94 "VPN",
95 "VR",
96 "WAN",
97 "XML",
98};
Ben Hamilton52161a52017-11-13 23:54:31 +000099
Yan Zhang75b3b542018-01-30 01:44:00 +0000100/// For now we will only fix 'CamelCase' or 'abc_CamelCase' property to
101/// 'camelCase' or 'abc_camelCase'. For other cases the users need to
Ben Hamilton52161a52017-11-13 23:54:31 +0000102/// come up with a proper name by their own.
103/// FIXME: provide fix for snake_case to snakeCase
Yan Zhang75b3b542018-01-30 01:44:00 +0000104FixItHint generateFixItHint(const ObjCPropertyDecl *Decl, NamingStyle Style) {
105 auto Name = Decl->getName();
106 auto NewName = Decl->getName().str();
107 size_t Index = 0;
108 if (Style == CategoryProperty) {
109 Index = Name.find_first_of('_') + 1;
110 NewName.replace(0, Index - 1, Name.substr(0, Index - 1).lower());
111 }
112 if (Index < Name.size()) {
113 NewName[Index] = tolower(NewName[Index]);
114 if (NewName != Name) {
115 return FixItHint::CreateReplacement(
116 CharSourceRange::getTokenRange(SourceRange(Decl->getLocation())),
117 llvm::StringRef(NewName));
118 }
Ben Hamilton52161a52017-11-13 23:54:31 +0000119 }
120 return FixItHint();
121}
122
Yan Zhangae5bc5ae2018-02-06 21:40:38 +0000123std::string AcronymsGroupRegex(llvm::ArrayRef<std::string> EscapedAcronyms) {
124 return "(" +
125 llvm::join(EscapedAcronyms.begin(), EscapedAcronyms.end(), "s?|") +
126 "s?)";
127}
128
Yan Zhang75b3b542018-01-30 01:44:00 +0000129std::string validPropertyNameRegex(llvm::ArrayRef<std::string> EscapedAcronyms,
130 bool UsedInMatcher) {
Ben Hamilton52161a52017-11-13 23:54:31 +0000131 // Allow any of these names:
132 // foo
133 // fooBar
134 // url
135 // urlString
136 // URL
137 // URLString
Yan Zhang8c298d22018-01-17 00:19:35 +0000138 // bundleID
Yan Zhang75b3b542018-01-30 01:44:00 +0000139 std::string StartMatcher = UsedInMatcher ? "::" : "^";
Yan Zhangae5bc5ae2018-02-06 21:40:38 +0000140 std::string AcronymsMatcher = AcronymsGroupRegex(EscapedAcronyms);
141 return StartMatcher + "(" + AcronymsMatcher + "[A-Z]?)?[a-z]+[a-z0-9]*(" +
142 AcronymsMatcher + "|([A-Z][a-z0-9]+))*$";
Yan Zhang75b3b542018-01-30 01:44:00 +0000143}
144
145bool hasCategoryPropertyPrefix(llvm::StringRef PropertyName) {
146 auto RegexExp = llvm::Regex("^[a-zA-Z]+_[a-zA-Z0-9][a-zA-Z0-9_]+$");
147 return RegexExp.match(PropertyName);
148}
149
150bool prefixedPropertyNameValid(llvm::StringRef PropertyName,
151 llvm::ArrayRef<std::string> Acronyms) {
152 size_t Start = PropertyName.find_first_of('_');
153 assert(Start != llvm::StringRef::npos && Start + 1 < PropertyName.size());
154 auto Prefix = PropertyName.substr(0, Start);
155 if (Prefix.lower() != Prefix) {
156 return false;
157 }
158 auto RegexExp =
159 llvm::Regex(llvm::StringRef(validPropertyNameRegex(Acronyms, false)));
160 return RegexExp.match(PropertyName.substr(Start + 1));
Ben Hamilton52161a52017-11-13 23:54:31 +0000161}
162} // namespace
163
164PropertyDeclarationCheck::PropertyDeclarationCheck(StringRef Name,
165 ClangTidyContext *Context)
166 : ClangTidyCheck(Name, Context),
Ben Hamiltonf94c10d2018-01-22 15:45:25 +0000167 SpecialAcronyms(
168 utils::options::parseStringList(Options.get("Acronyms", ""))),
Yan Zhang75b3b542018-01-30 01:44:00 +0000169 IncludeDefaultAcronyms(Options.get("IncludeDefaultAcronyms", true)),
170 EscapedAcronyms() {}
Ben Hamilton52161a52017-11-13 23:54:31 +0000171
172void PropertyDeclarationCheck::registerMatchers(MatchFinder *Finder) {
Yan Zhangc7faee72018-03-07 18:59:25 +0000173 // this check should only be applied to ObjC sources.
174 if (!getLangOpts().ObjC1 && !getLangOpts().ObjC2) {
175 return;
176 }
Ben Hamiltonf94c10d2018-01-22 15:45:25 +0000177 if (IncludeDefaultAcronyms) {
178 EscapedAcronyms.reserve(llvm::array_lengthof(DefaultSpecialAcronyms) +
179 SpecialAcronyms.size());
180 // No need to regex-escape the default acronyms.
181 EscapedAcronyms.insert(EscapedAcronyms.end(),
182 std::begin(DefaultSpecialAcronyms),
183 std::end(DefaultSpecialAcronyms));
184 } else {
185 EscapedAcronyms.reserve(SpecialAcronyms.size());
186 }
187 // In case someone defines a prefix which includes a regex
188 // special character, regex-escape all the user-defined prefixes.
189 std::transform(SpecialAcronyms.begin(), SpecialAcronyms.end(),
190 std::back_inserter(EscapedAcronyms),
191 [](const std::string &s) { return llvm::Regex::escape(s); });
Ben Hamilton52161a52017-11-13 23:54:31 +0000192 Finder->addMatcher(
193 objcPropertyDecl(
194 // the property name should be in Lower Camel Case like
195 // 'lowerCamelCase'
Yan Zhang75b3b542018-01-30 01:44:00 +0000196 unless(matchesName(validPropertyNameRegex(EscapedAcronyms, true))))
Ben Hamilton52161a52017-11-13 23:54:31 +0000197 .bind("property"),
198 this);
199}
200
201void PropertyDeclarationCheck::check(const MatchFinder::MatchResult &Result) {
202 const auto *MatchedDecl =
203 Result.Nodes.getNodeAs<ObjCPropertyDecl>("property");
204 assert(MatchedDecl->getName().size() > 0);
Yan Zhang75b3b542018-01-30 01:44:00 +0000205 auto *DeclContext = MatchedDecl->getDeclContext();
206 auto *CategoryDecl = llvm::dyn_cast<ObjCCategoryDecl>(DeclContext);
207 if (CategoryDecl != nullptr &&
208 hasCategoryPropertyPrefix(MatchedDecl->getName())) {
209 if (!prefixedPropertyNameValid(MatchedDecl->getName(), EscapedAcronyms) ||
210 CategoryDecl->IsClassExtension()) {
211 NamingStyle Style = CategoryDecl->IsClassExtension() ? StandardProperty
212 : CategoryProperty;
213 diag(MatchedDecl->getLocation(),
214 "property name '%0' not using lowerCamelCase style or not prefixed "
215 "in a category, according to the Apple Coding Guidelines")
216 << MatchedDecl->getName() << generateFixItHint(MatchedDecl, Style);
217 }
218 return;
219 }
Ben Hamilton52161a52017-11-13 23:54:31 +0000220 diag(MatchedDecl->getLocation(),
Yan Zhang75b3b542018-01-30 01:44:00 +0000221 "property name '%0' not using lowerCamelCase style or not prefixed in "
222 "a category, according to the Apple Coding Guidelines")
223 << MatchedDecl->getName()
224 << generateFixItHint(MatchedDecl, StandardProperty);
Ben Hamilton52161a52017-11-13 23:54:31 +0000225}
226
227void PropertyDeclarationCheck::storeOptions(ClangTidyOptions::OptionMap &Opts) {
228 Options.store(Opts, "Acronyms",
229 utils::options::serializeStringList(SpecialAcronyms));
Ben Hamiltonf94c10d2018-01-22 15:45:25 +0000230 Options.store(Opts, "IncludeDefaultAcronyms", IncludeDefaultAcronyms);
Ben Hamilton52161a52017-11-13 23:54:31 +0000231}
232
233} // namespace objc
234} // namespace tidy
235} // namespace clang