clang-tools 22.0.0git
PropertyDeclarationCheck.cpp
Go to the documentation of this file.
1//===----------------------------------------------------------------------===//
2//
3// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4// See https://llvm.org/LICENSE.txt for license information.
5// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6//
7//===----------------------------------------------------------------------===//
8
10#include "clang/AST/ASTContext.h"
11#include "clang/ASTMatchers/ASTMatchFinder.h"
12#include "llvm/Support/Regex.h"
13
14using namespace clang::ast_matchers;
15
16namespace clang::tidy::objc {
17
18namespace {
19
20// For StandardProperty the naming style is 'lowerCamelCase'.
21// For CategoryProperty especially in categories of system class,
22// to avoid naming conflict, the suggested naming style is
23// 'abc_lowerCamelCase' (adding lowercase prefix followed by '_').
24// Regardless of the style, all acronyms and initialisms should be capitalized.
25enum NamingStyle {
26 StandardProperty = 1,
27 CategoryProperty = 2,
28};
29
30} // namespace
31
32/// For now we will only fix 'CamelCase' or 'abc_CamelCase' property to
33/// 'camelCase' or 'abc_camelCase'. For other cases the users need to
34/// come up with a proper name by their own.
35/// FIXME: provide fix for snake_case to snakeCase
36static FixItHint generateFixItHint(const ObjCPropertyDecl *Decl,
37 NamingStyle Style) {
38 auto Name = Decl->getName();
39 auto NewName = Decl->getName().str();
40 size_t Index = 0;
41 if (Style == CategoryProperty) {
42 Index = Name.find_first_of('_') + 1;
43 NewName.replace(0, Index - 1, Name.substr(0, Index - 1).lower());
44 }
45 if (Index < Name.size()) {
46 NewName[Index] = tolower(NewName[Index]);
47 if (NewName != Name) {
48 return FixItHint::CreateReplacement(
49 CharSourceRange::getTokenRange(SourceRange(Decl->getLocation())),
50 llvm::StringRef(NewName));
51 }
52 }
53 return {};
54}
55
56static std::string validPropertyNameRegex(bool UsedInMatcher) {
57 // Allow any of these names:
58 // foo
59 // fooBar
60 // url
61 // urlString
62 // ID
63 // IDs
64 // URL
65 // URLString
66 // bundleID
67 // CIColor
68 //
69 // Disallow names of this form:
70 // LongString
71 //
72 // aRbITRaRyCapS is allowed to avoid generating false positives for names
73 // like isVitaminBSupplement, CProgrammingLanguage, and isBeforeM.
74 std::string StartMatcher = UsedInMatcher ? "::" : "^";
75 return StartMatcher + "([a-z]|[A-Z][A-Z0-9])[a-z0-9A-Z]*$";
76}
77
78static bool hasCategoryPropertyPrefix(llvm::StringRef PropertyName) {
79 auto RegexExp =
80 llvm::Regex("^[a-zA-Z][a-zA-Z0-9]*_[a-zA-Z0-9][a-zA-Z0-9_]+$");
81 return RegexExp.match(PropertyName);
82}
83
84static bool prefixedPropertyNameValid(llvm::StringRef PropertyName) {
85 size_t Start = PropertyName.find_first_of('_');
86 assert(Start != llvm::StringRef::npos && Start + 1 < PropertyName.size());
87 auto Prefix = PropertyName.substr(0, Start);
88 if (Prefix.lower() != Prefix) {
89 return false;
90 }
91 auto RegexExp = llvm::Regex(llvm::StringRef(validPropertyNameRegex(false)));
92 return RegexExp.match(PropertyName.substr(Start + 1));
93}
94
96 Finder->addMatcher(objcPropertyDecl(
97 // the property name should be in Lower Camel Case like
98 // 'lowerCamelCase'
99 unless(matchesName(validPropertyNameRegex(true))))
100 .bind("property"),
101 this);
102}
103
104void PropertyDeclarationCheck::check(const MatchFinder::MatchResult &Result) {
105 const auto *MatchedDecl =
106 Result.Nodes.getNodeAs<ObjCPropertyDecl>("property");
107 assert(!MatchedDecl->getName().empty());
108 auto *DeclContext = MatchedDecl->getDeclContext();
109 auto *CategoryDecl = llvm::dyn_cast<ObjCCategoryDecl>(DeclContext);
110
111 if (CategoryDecl != nullptr &&
112 hasCategoryPropertyPrefix(MatchedDecl->getName())) {
113 if (!prefixedPropertyNameValid(MatchedDecl->getName()) ||
114 CategoryDecl->IsClassExtension()) {
115 NamingStyle Style = CategoryDecl->IsClassExtension() ? StandardProperty
116 : CategoryProperty;
117 diag(MatchedDecl->getLocation(),
118 "property name '%0' not using lowerCamelCase style or not prefixed "
119 "in a category, according to the Apple Coding Guidelines")
120 << MatchedDecl->getName() << generateFixItHint(MatchedDecl, Style);
121 }
122 return;
123 }
124 diag(MatchedDecl->getLocation(),
125 "property name '%0' not using lowerCamelCase style or not prefixed in "
126 "a category, according to the Apple Coding Guidelines")
127 << MatchedDecl->getName()
128 << generateFixItHint(MatchedDecl, StandardProperty);
129}
130
131} // namespace clang::tidy::objc
void check(const ast_matchers::MatchFinder::MatchResult &Result) override
void registerMatchers(ast_matchers::MatchFinder *Finder) override
static FixItHint generateFixItHint(const ObjCPropertyDecl *Decl, NamingStyle Style)
For now we will only fix 'CamelCase' or 'abc_CamelCase' property to 'camelCase' or 'abc_camelCase'.
static bool hasCategoryPropertyPrefix(llvm::StringRef PropertyName)
static bool prefixedPropertyNameValid(llvm::StringRef PropertyName)
static std::string validPropertyNameRegex(bool UsedInMatcher)