blob: ee08e2f0323c59d1daf85328fd500df5238c7cd6 [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",
53 "HD",
54 "HDR",
55 "HTML",
56 "HTTP",
57 "HTTPS",
58 "HUD",
59 "ID",
60 "JPG",
61 "JS",
62 "LAN",
63 "LZW",
64 "MDNS",
65 "MIDI",
66 "OS",
67 "PDF",
68 "PIN",
69 "PNG",
70 "POI",
71 "PSTN",
72 "PTR",
73 "QA",
74 "QOS",
75 "RGB",
76 "RGBA",
77 "RGBX",
78 "ROM",
79 "RPC",
80 "RTF",
81 "RTL",
82 "SDK",
83 "SSO",
84 "TCP",
85 "TIFF",
86 "TTS",
87 "UI",
88 "URI",
89 "URL",
90 "VC",
91 "VOIP",
92 "VPN",
93 "VR",
94 "WAN",
95 "XML",
96};
Ben Hamilton52161a52017-11-13 23:54:31 +000097
Yan Zhang75b3b542018-01-30 01:44:00 +000098/// For now we will only fix 'CamelCase' or 'abc_CamelCase' property to
99/// 'camelCase' or 'abc_camelCase'. For other cases the users need to
Ben Hamilton52161a52017-11-13 23:54:31 +0000100/// come up with a proper name by their own.
101/// FIXME: provide fix for snake_case to snakeCase
Yan Zhang75b3b542018-01-30 01:44:00 +0000102FixItHint generateFixItHint(const ObjCPropertyDecl *Decl, NamingStyle Style) {
103 auto Name = Decl->getName();
104 auto NewName = Decl->getName().str();
105 size_t Index = 0;
106 if (Style == CategoryProperty) {
107 Index = Name.find_first_of('_') + 1;
108 NewName.replace(0, Index - 1, Name.substr(0, Index - 1).lower());
109 }
110 if (Index < Name.size()) {
111 NewName[Index] = tolower(NewName[Index]);
112 if (NewName != Name) {
113 return FixItHint::CreateReplacement(
114 CharSourceRange::getTokenRange(SourceRange(Decl->getLocation())),
115 llvm::StringRef(NewName));
116 }
Ben Hamilton52161a52017-11-13 23:54:31 +0000117 }
118 return FixItHint();
119}
120
Yan Zhangae5bc5ae2018-02-06 21:40:38 +0000121std::string AcronymsGroupRegex(llvm::ArrayRef<std::string> EscapedAcronyms) {
122 return "(" +
123 llvm::join(EscapedAcronyms.begin(), EscapedAcronyms.end(), "s?|") +
124 "s?)";
125}
126
Yan Zhang75b3b542018-01-30 01:44:00 +0000127std::string validPropertyNameRegex(llvm::ArrayRef<std::string> EscapedAcronyms,
128 bool UsedInMatcher) {
Ben Hamilton52161a52017-11-13 23:54:31 +0000129 // Allow any of these names:
130 // foo
131 // fooBar
132 // url
133 // urlString
134 // URL
135 // URLString
Yan Zhang8c298d22018-01-17 00:19:35 +0000136 // bundleID
Yan Zhang75b3b542018-01-30 01:44:00 +0000137 std::string StartMatcher = UsedInMatcher ? "::" : "^";
Yan Zhangae5bc5ae2018-02-06 21:40:38 +0000138 std::string AcronymsMatcher = AcronymsGroupRegex(EscapedAcronyms);
139 return StartMatcher + "(" + AcronymsMatcher + "[A-Z]?)?[a-z]+[a-z0-9]*(" +
140 AcronymsMatcher + "|([A-Z][a-z0-9]+))*$";
Yan Zhang75b3b542018-01-30 01:44:00 +0000141}
142
143bool hasCategoryPropertyPrefix(llvm::StringRef PropertyName) {
144 auto RegexExp = llvm::Regex("^[a-zA-Z]+_[a-zA-Z0-9][a-zA-Z0-9_]+$");
145 return RegexExp.match(PropertyName);
146}
147
148bool prefixedPropertyNameValid(llvm::StringRef PropertyName,
149 llvm::ArrayRef<std::string> Acronyms) {
150 size_t Start = PropertyName.find_first_of('_');
151 assert(Start != llvm::StringRef::npos && Start + 1 < PropertyName.size());
152 auto Prefix = PropertyName.substr(0, Start);
153 if (Prefix.lower() != Prefix) {
154 return false;
155 }
156 auto RegexExp =
157 llvm::Regex(llvm::StringRef(validPropertyNameRegex(Acronyms, false)));
158 return RegexExp.match(PropertyName.substr(Start + 1));
Ben Hamilton52161a52017-11-13 23:54:31 +0000159}
160} // namespace
161
162PropertyDeclarationCheck::PropertyDeclarationCheck(StringRef Name,
163 ClangTidyContext *Context)
164 : ClangTidyCheck(Name, Context),
Ben Hamiltonf94c10d2018-01-22 15:45:25 +0000165 SpecialAcronyms(
166 utils::options::parseStringList(Options.get("Acronyms", ""))),
Yan Zhang75b3b542018-01-30 01:44:00 +0000167 IncludeDefaultAcronyms(Options.get("IncludeDefaultAcronyms", true)),
168 EscapedAcronyms() {}
Ben Hamilton52161a52017-11-13 23:54:31 +0000169
170void PropertyDeclarationCheck::registerMatchers(MatchFinder *Finder) {
Ben Hamiltonf94c10d2018-01-22 15:45:25 +0000171 if (IncludeDefaultAcronyms) {
172 EscapedAcronyms.reserve(llvm::array_lengthof(DefaultSpecialAcronyms) +
173 SpecialAcronyms.size());
174 // No need to regex-escape the default acronyms.
175 EscapedAcronyms.insert(EscapedAcronyms.end(),
176 std::begin(DefaultSpecialAcronyms),
177 std::end(DefaultSpecialAcronyms));
178 } else {
179 EscapedAcronyms.reserve(SpecialAcronyms.size());
180 }
181 // In case someone defines a prefix which includes a regex
182 // special character, regex-escape all the user-defined prefixes.
183 std::transform(SpecialAcronyms.begin(), SpecialAcronyms.end(),
184 std::back_inserter(EscapedAcronyms),
185 [](const std::string &s) { return llvm::Regex::escape(s); });
Ben Hamilton52161a52017-11-13 23:54:31 +0000186 Finder->addMatcher(
187 objcPropertyDecl(
188 // the property name should be in Lower Camel Case like
189 // 'lowerCamelCase'
Yan Zhang75b3b542018-01-30 01:44:00 +0000190 unless(matchesName(validPropertyNameRegex(EscapedAcronyms, true))))
Ben Hamilton52161a52017-11-13 23:54:31 +0000191 .bind("property"),
192 this);
193}
194
195void PropertyDeclarationCheck::check(const MatchFinder::MatchResult &Result) {
196 const auto *MatchedDecl =
197 Result.Nodes.getNodeAs<ObjCPropertyDecl>("property");
198 assert(MatchedDecl->getName().size() > 0);
Yan Zhang75b3b542018-01-30 01:44:00 +0000199 auto *DeclContext = MatchedDecl->getDeclContext();
200 auto *CategoryDecl = llvm::dyn_cast<ObjCCategoryDecl>(DeclContext);
201 if (CategoryDecl != nullptr &&
202 hasCategoryPropertyPrefix(MatchedDecl->getName())) {
203 if (!prefixedPropertyNameValid(MatchedDecl->getName(), EscapedAcronyms) ||
204 CategoryDecl->IsClassExtension()) {
205 NamingStyle Style = CategoryDecl->IsClassExtension() ? StandardProperty
206 : CategoryProperty;
207 diag(MatchedDecl->getLocation(),
208 "property name '%0' not using lowerCamelCase style or not prefixed "
209 "in a category, according to the Apple Coding Guidelines")
210 << MatchedDecl->getName() << generateFixItHint(MatchedDecl, Style);
211 }
212 return;
213 }
Ben Hamilton52161a52017-11-13 23:54:31 +0000214 diag(MatchedDecl->getLocation(),
Yan Zhang75b3b542018-01-30 01:44:00 +0000215 "property name '%0' not using lowerCamelCase style or not prefixed in "
216 "a category, according to the Apple Coding Guidelines")
217 << MatchedDecl->getName()
218 << generateFixItHint(MatchedDecl, StandardProperty);
Ben Hamilton52161a52017-11-13 23:54:31 +0000219}
220
221void PropertyDeclarationCheck::storeOptions(ClangTidyOptions::OptionMap &Opts) {
222 Options.store(Opts, "Acronyms",
223 utils::options::serializeStringList(SpecialAcronyms));
Ben Hamiltonf94c10d2018-01-22 15:45:25 +0000224 Options.store(Opts, "IncludeDefaultAcronyms", IncludeDefaultAcronyms);
Ben Hamilton52161a52017-11-13 23:54:31 +0000225}
226
227} // namespace objc
228} // namespace tidy
229} // namespace clang