blob: 32f0dcae76f1250ef9ce9204681e374f6b6fcfa3 [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"
Ben Hamiltonf94c10d2018-01-22 15:45:25 +000015#include "llvm/ADT/STLExtras.h"
Yan Zhang75b3b542018-01-30 01:44:00 +000016#include "clang/Basic/CharInfo.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 Zhang75b3b542018-01-30 01:44:00 +0000121std::string validPropertyNameRegex(llvm::ArrayRef<std::string> EscapedAcronyms,
122 bool UsedInMatcher) {
Ben Hamilton52161a52017-11-13 23:54:31 +0000123 // Allow any of these names:
124 // foo
125 // fooBar
126 // url
127 // urlString
128 // URL
129 // URLString
Yan Zhang8c298d22018-01-17 00:19:35 +0000130 // bundleID
Yan Zhang75b3b542018-01-30 01:44:00 +0000131 std::string StartMatcher = UsedInMatcher ? "::" : "^";
132
133 return StartMatcher + "((" +
134 llvm::join(EscapedAcronyms.begin(), EscapedAcronyms.end(), "|") +
135 ")[A-Z]?)?[a-z]+[a-z0-9]*([A-Z][a-z0-9]+)*" + "(" +
136 llvm::join(EscapedAcronyms.begin(), EscapedAcronyms.end(), "|") +
137 ")?$";
138}
139
140bool hasCategoryPropertyPrefix(llvm::StringRef PropertyName) {
141 auto RegexExp = llvm::Regex("^[a-zA-Z]+_[a-zA-Z0-9][a-zA-Z0-9_]+$");
142 return RegexExp.match(PropertyName);
143}
144
145bool prefixedPropertyNameValid(llvm::StringRef PropertyName,
146 llvm::ArrayRef<std::string> Acronyms) {
147 size_t Start = PropertyName.find_first_of('_');
148 assert(Start != llvm::StringRef::npos && Start + 1 < PropertyName.size());
149 auto Prefix = PropertyName.substr(0, Start);
150 if (Prefix.lower() != Prefix) {
151 return false;
152 }
153 auto RegexExp =
154 llvm::Regex(llvm::StringRef(validPropertyNameRegex(Acronyms, false)));
155 return RegexExp.match(PropertyName.substr(Start + 1));
Ben Hamilton52161a52017-11-13 23:54:31 +0000156}
157} // namespace
158
159PropertyDeclarationCheck::PropertyDeclarationCheck(StringRef Name,
160 ClangTidyContext *Context)
161 : ClangTidyCheck(Name, Context),
Ben Hamiltonf94c10d2018-01-22 15:45:25 +0000162 SpecialAcronyms(
163 utils::options::parseStringList(Options.get("Acronyms", ""))),
Yan Zhang75b3b542018-01-30 01:44:00 +0000164 IncludeDefaultAcronyms(Options.get("IncludeDefaultAcronyms", true)),
165 EscapedAcronyms() {}
Ben Hamilton52161a52017-11-13 23:54:31 +0000166
167void PropertyDeclarationCheck::registerMatchers(MatchFinder *Finder) {
Ben Hamiltonf94c10d2018-01-22 15:45:25 +0000168 if (IncludeDefaultAcronyms) {
169 EscapedAcronyms.reserve(llvm::array_lengthof(DefaultSpecialAcronyms) +
170 SpecialAcronyms.size());
171 // No need to regex-escape the default acronyms.
172 EscapedAcronyms.insert(EscapedAcronyms.end(),
173 std::begin(DefaultSpecialAcronyms),
174 std::end(DefaultSpecialAcronyms));
175 } else {
176 EscapedAcronyms.reserve(SpecialAcronyms.size());
177 }
178 // In case someone defines a prefix which includes a regex
179 // special character, regex-escape all the user-defined prefixes.
180 std::transform(SpecialAcronyms.begin(), SpecialAcronyms.end(),
181 std::back_inserter(EscapedAcronyms),
182 [](const std::string &s) { return llvm::Regex::escape(s); });
Ben Hamilton52161a52017-11-13 23:54:31 +0000183 Finder->addMatcher(
184 objcPropertyDecl(
185 // the property name should be in Lower Camel Case like
186 // 'lowerCamelCase'
Yan Zhang75b3b542018-01-30 01:44:00 +0000187 unless(matchesName(validPropertyNameRegex(EscapedAcronyms, true))))
Ben Hamilton52161a52017-11-13 23:54:31 +0000188 .bind("property"),
189 this);
190}
191
192void PropertyDeclarationCheck::check(const MatchFinder::MatchResult &Result) {
193 const auto *MatchedDecl =
194 Result.Nodes.getNodeAs<ObjCPropertyDecl>("property");
195 assert(MatchedDecl->getName().size() > 0);
Yan Zhang75b3b542018-01-30 01:44:00 +0000196 auto *DeclContext = MatchedDecl->getDeclContext();
197 auto *CategoryDecl = llvm::dyn_cast<ObjCCategoryDecl>(DeclContext);
198 if (CategoryDecl != nullptr &&
199 hasCategoryPropertyPrefix(MatchedDecl->getName())) {
200 if (!prefixedPropertyNameValid(MatchedDecl->getName(), EscapedAcronyms) ||
201 CategoryDecl->IsClassExtension()) {
202 NamingStyle Style = CategoryDecl->IsClassExtension() ? StandardProperty
203 : CategoryProperty;
204 diag(MatchedDecl->getLocation(),
205 "property name '%0' not using lowerCamelCase style or not prefixed "
206 "in a category, according to the Apple Coding Guidelines")
207 << MatchedDecl->getName() << generateFixItHint(MatchedDecl, Style);
208 }
209 return;
210 }
Ben Hamilton52161a52017-11-13 23:54:31 +0000211 diag(MatchedDecl->getLocation(),
Yan Zhang75b3b542018-01-30 01:44:00 +0000212 "property name '%0' not using lowerCamelCase style or not prefixed in "
213 "a category, according to the Apple Coding Guidelines")
214 << MatchedDecl->getName()
215 << generateFixItHint(MatchedDecl, StandardProperty);
Ben Hamilton52161a52017-11-13 23:54:31 +0000216}
217
218void PropertyDeclarationCheck::storeOptions(ClangTidyOptions::OptionMap &Opts) {
219 Options.store(Opts, "Acronyms",
220 utils::options::serializeStringList(SpecialAcronyms));
Ben Hamiltonf94c10d2018-01-22 15:45:25 +0000221 Options.store(Opts, "IncludeDefaultAcronyms", IncludeDefaultAcronyms);
Ben Hamilton52161a52017-11-13 23:54:31 +0000222}
223
224} // namespace objc
225} // namespace tidy
226} // namespace clang