clang-tools  7.0.0
PropertyDeclarationCheck.cpp
Go to the documentation of this file.
1 //===--- 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 
11 #include <algorithm>
12 #include "../utils/OptionsUtils.h"
13 #include "clang/AST/ASTContext.h"
14 #include "clang/ASTMatchers/ASTMatchFinder.h"
15 #include "clang/Basic/CharInfo.h"
16 #include "llvm/ADT/STLExtras.h"
17 #include "llvm/ADT/StringExtras.h"
18 #include "llvm/Support/Regex.h"
19 
20 using namespace clang::ast_matchers;
21 
22 namespace clang {
23 namespace tidy {
24 namespace objc {
25 
26 namespace {
27 
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 '_').
33  StandardProperty = 1,
34  CategoryProperty = 2,
35 };
36 
37 /// The acronyms are aggregated from multiple sources including
38 /// https://developer.apple.com/library/content/documentation/Cocoa/Conceptual/CodingGuidelines/Articles/APIAbbreviations.html#//apple_ref/doc/uid/20001285-BCIHCGAE
39 ///
40 /// Keep this list sorted.
41 constexpr llvm::StringLiteral DefaultSpecialAcronyms[] = {
42  "[2-9]G",
43  "ACL",
44  "API",
45  "AR",
46  "ARGB",
47  "ASCII",
48  "AV",
49  "BGRA",
50  "CA",
51  "CF",
52  "CG",
53  "CI",
54  "CRC",
55  "CV",
56  "CMYK",
57  "DNS",
58  "FPS",
59  "FTP",
60  "GIF",
61  "GL",
62  "GPS",
63  "GUID",
64  "HD",
65  "HDR",
66  "HMAC",
67  "HTML",
68  "HTTP",
69  "HTTPS",
70  "HUD",
71  "ID",
72  "JPG",
73  "JS",
74  "LAN",
75  "LZW",
76  "MAC",
77  "MD",
78  "MDNS",
79  "MIDI",
80  "NS",
81  "OS",
82  "PDF",
83  "PIN",
84  "PNG",
85  "POI",
86  "PSTN",
87  "PTR",
88  "QA",
89  "QOS",
90  "RGB",
91  "RGBA",
92  "RGBX",
93  "RIPEMD",
94  "ROM",
95  "RPC",
96  "RTF",
97  "RTL",
98  "SC",
99  "SDK",
100  "SHA",
101  "SQL",
102  "SSO",
103  "TCP",
104  "TIFF",
105  "TTS",
106  "UI",
107  "URI",
108  "URL",
109  "UUID",
110  "VC",
111  "VOIP",
112  "VPN",
113  "VR",
114  "W",
115  "WAN",
116  "X",
117  "XML",
118  "Y",
119  "Z",
120 };
121 
122 /// For now we will only fix 'CamelCase' or 'abc_CamelCase' property to
123 /// 'camelCase' or 'abc_camelCase'. For other cases the users need to
124 /// come up with a proper name by their own.
125 /// FIXME: provide fix for snake_case to snakeCase
126 FixItHint generateFixItHint(const ObjCPropertyDecl *Decl, NamingStyle Style) {
127  auto Name = Decl->getName();
128  auto NewName = Decl->getName().str();
129  size_t Index = 0;
130  if (Style == CategoryProperty) {
131  Index = Name.find_first_of('_') + 1;
132  NewName.replace(0, Index - 1, Name.substr(0, Index - 1).lower());
133  }
134  if (Index < Name.size()) {
135  NewName[Index] = tolower(NewName[Index]);
136  if (NewName != Name) {
137  return FixItHint::CreateReplacement(
138  CharSourceRange::getTokenRange(SourceRange(Decl->getLocation())),
139  llvm::StringRef(NewName));
140  }
141  }
142  return FixItHint();
143 }
144 
145 std::string AcronymsGroupRegex(llvm::ArrayRef<std::string> EscapedAcronyms) {
146  return "(" +
147  llvm::join(EscapedAcronyms.begin(), EscapedAcronyms.end(), "s?|") +
148  "s?)";
149 }
150 
151 std::string validPropertyNameRegex(llvm::ArrayRef<std::string> EscapedAcronyms,
152  bool UsedInMatcher) {
153  // Allow any of these names:
154  // foo
155  // fooBar
156  // url
157  // urlString
158  // URL
159  // URLString
160  // bundleID
161  std::string StartMatcher = UsedInMatcher ? "::" : "^";
162  std::string AcronymsMatcher = AcronymsGroupRegex(EscapedAcronyms);
163  return StartMatcher + "(" + AcronymsMatcher + "[A-Z]?)?[a-z]+[a-z0-9]*(" +
164  AcronymsMatcher + "|([A-Z][a-z0-9]+)|A|I)*$";
165 }
166 
167 bool hasCategoryPropertyPrefix(llvm::StringRef PropertyName) {
168  auto RegexExp = llvm::Regex("^[a-zA-Z]+_[a-zA-Z0-9][a-zA-Z0-9_]+$");
169  return RegexExp.match(PropertyName);
170 }
171 
172 bool prefixedPropertyNameValid(llvm::StringRef PropertyName,
173  llvm::ArrayRef<std::string> Acronyms) {
174  size_t Start = PropertyName.find_first_of('_');
175  assert(Start != llvm::StringRef::npos && Start + 1 < PropertyName.size());
176  auto Prefix = PropertyName.substr(0, Start);
177  if (Prefix.lower() != Prefix) {
178  return false;
179  }
180  auto RegexExp =
181  llvm::Regex(llvm::StringRef(validPropertyNameRegex(Acronyms, false)));
182  return RegexExp.match(PropertyName.substr(Start + 1));
183 }
184 } // namespace
185 
186 PropertyDeclarationCheck::PropertyDeclarationCheck(StringRef Name,
187  ClangTidyContext *Context)
188  : ClangTidyCheck(Name, Context),
189  SpecialAcronyms(
190  utils::options::parseStringList(Options.get("Acronyms", ""))),
191  IncludeDefaultAcronyms(Options.get("IncludeDefaultAcronyms", true)),
192  EscapedAcronyms() {}
193 
195  // this check should only be applied to ObjC sources.
196  if (!getLangOpts().ObjC1 && !getLangOpts().ObjC2) {
197  return;
198  }
199  if (IncludeDefaultAcronyms) {
200  EscapedAcronyms.reserve(llvm::array_lengthof(DefaultSpecialAcronyms) +
201  SpecialAcronyms.size());
202  // No need to regex-escape the default acronyms.
203  EscapedAcronyms.insert(EscapedAcronyms.end(),
204  std::begin(DefaultSpecialAcronyms),
205  std::end(DefaultSpecialAcronyms));
206  } else {
207  EscapedAcronyms.reserve(SpecialAcronyms.size());
208  }
209  // In case someone defines a prefix which includes a regex
210  // special character, regex-escape all the user-defined prefixes.
211  std::transform(SpecialAcronyms.begin(), SpecialAcronyms.end(),
212  std::back_inserter(EscapedAcronyms),
213  [](const std::string &s) { return llvm::Regex::escape(s); });
214  Finder->addMatcher(
215  objcPropertyDecl(
216  // the property name should be in Lower Camel Case like
217  // 'lowerCamelCase'
218  unless(matchesName(validPropertyNameRegex(EscapedAcronyms, true))))
219  .bind("property"),
220  this);
221 }
222 
223 void PropertyDeclarationCheck::check(const MatchFinder::MatchResult &Result) {
224  const auto *MatchedDecl =
225  Result.Nodes.getNodeAs<ObjCPropertyDecl>("property");
226  assert(MatchedDecl->getName().size() > 0);
227  auto *DeclContext = MatchedDecl->getDeclContext();
228  auto *CategoryDecl = llvm::dyn_cast<ObjCCategoryDecl>(DeclContext);
229 
230  auto AcronymsRegex =
231  llvm::Regex("^" + AcronymsGroupRegex(EscapedAcronyms) + "$");
232  if (AcronymsRegex.match(MatchedDecl->getName())) {
233  return;
234  }
235  if (CategoryDecl != nullptr &&
236  hasCategoryPropertyPrefix(MatchedDecl->getName())) {
237  if (!prefixedPropertyNameValid(MatchedDecl->getName(), EscapedAcronyms) ||
238  CategoryDecl->IsClassExtension()) {
239  NamingStyle Style = CategoryDecl->IsClassExtension() ? StandardProperty
240  : CategoryProperty;
241  diag(MatchedDecl->getLocation(),
242  "property name '%0' not using lowerCamelCase style or not prefixed "
243  "in a category, according to the Apple Coding Guidelines")
244  << MatchedDecl->getName() << generateFixItHint(MatchedDecl, Style);
245  }
246  return;
247  }
248  diag(MatchedDecl->getLocation(),
249  "property name '%0' not using lowerCamelCase style or not prefixed in "
250  "a category, according to the Apple Coding Guidelines")
251  << MatchedDecl->getName()
252  << generateFixItHint(MatchedDecl, StandardProperty);
253 }
254 
256  Options.store(Opts, "Acronyms",
257  utils::options::serializeStringList(SpecialAcronyms));
258  Options.store(Opts, "IncludeDefaultAcronyms", IncludeDefaultAcronyms);
259 }
260 
261 } // namespace objc
262 } // namespace tidy
263 } // namespace clang
llvm::StringRef Name
void store(ClangTidyOptions::OptionMap &Options, StringRef LocalName, StringRef Value) const
Stores an option with the check-local name LocalName with string value Value to Options.
Definition: ClangTidy.cpp:460
void storeOptions(ClangTidyOptions::OptionMap &Options) override
Should store all options supported by this check with their current values or default values for opti...
std::string serializeStringList(ArrayRef< std::string > Strings)
Serialize a sequence of names that can be parsed by parseStringList.
LangOptions getLangOpts() const
Returns the language options from the context.
Definition: ClangTidy.h:187
Base class for all clang-tidy checks.
Definition: ClangTidy.h:127
std::vector< std::string > parseStringList(StringRef Option)
Parse a semicolon separated list of strings.
std::map< std::string, std::string > OptionMap
===– Representation.cpp - ClangDoc Representation --------—*- C++ -*-===//
Every ClangTidyCheck reports errors through a DiagnosticsEngine provided by this context.
void check(const ast_matchers::MatchFinder::MatchResult &Result) override
ClangTidyChecks that register ASTMatchers should do the actual work in here.
static std::string join(ArrayRef< SpecialMemberFunctionsCheck::SpecialMemberFunctionKind > SMFS, llvm::StringRef AndOr)
void registerMatchers(ast_matchers::MatchFinder *Finder) override
Override this to register AST matchers with Finder.
DiagnosticBuilder diag(SourceLocation Loc, StringRef Description, DiagnosticIDs::Level Level=DiagnosticIDs::Warning)
Add a diagnostic with the check&#39;s name.
Definition: ClangTidy.cpp:427