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 size_t UnderScorePos = Name.find_first_of('_');
43 if (UnderScorePos != llvm::StringRef::npos) {
44 Index = UnderScorePos + 1;
45 NewName.replace(0, Index - 1, Name.substr(0, Index - 1).lower());
46 }
47 }
48 if (Index < Name.size()) {
49 NewName[Index] = tolower(NewName[Index]);
50 if (NewName != Name) {
51 return FixItHint::CreateReplacement(
52 CharSourceRange::getTokenRange(SourceRange(Decl->getLocation())),
53 llvm::StringRef(NewName));
54 }
55 }
56 return {};
57}
58
59static std::string validPropertyNameRegex(bool UsedInMatcher) {
60 // Allow any of these names:
61 // foo
62 // fooBar
63 // url
64 // urlString
65 // ID
66 // IDs
67 // URL
68 // URLString
69 // bundleID
70 // CIColor
71 //
72 // Disallow names of this form:
73 // LongString
74 //
75 // aRbITRaRyCapS is allowed to avoid generating false positives for names
76 // like isVitaminBSupplement, CProgrammingLanguage, and isBeforeM.
77 std::string StartMatcher = UsedInMatcher ? "::" : "^";
78 return StartMatcher + "([a-z]|[A-Z][A-Z0-9])[a-z0-9A-Z]*$";
79}
80
81static bool hasCategoryPropertyPrefix(llvm::StringRef PropertyName) {
82 auto RegexExp =
83 llvm::Regex("^[a-zA-Z][a-zA-Z0-9]*_[a-zA-Z0-9][a-zA-Z0-9_]+$");
84 return RegexExp.match(PropertyName);
85}
86
87static bool prefixedPropertyNameValid(llvm::StringRef PropertyName) {
88 size_t Start = PropertyName.find_first_of('_');
89 assert(Start != llvm::StringRef::npos && Start + 1 < PropertyName.size());
90 auto Prefix = PropertyName.substr(0, Start);
91 if (Prefix.lower() != Prefix) {
92 return false;
93 }
94 auto RegexExp = llvm::Regex(llvm::StringRef(validPropertyNameRegex(false)));
95 return RegexExp.match(PropertyName.substr(Start + 1));
96}
97
99 Finder->addMatcher(objcPropertyDecl(
100 // the property name should be in Lower Camel Case like
101 // 'lowerCamelCase'
102 unless(matchesName(validPropertyNameRegex(true))))
103 .bind("property"),
104 this);
105}
106
107void PropertyDeclarationCheck::check(const MatchFinder::MatchResult &Result) {
108 const auto *MatchedDecl =
109 Result.Nodes.getNodeAs<ObjCPropertyDecl>("property");
110 assert(!MatchedDecl->getName().empty());
111 auto *DeclContext = MatchedDecl->getDeclContext();
112 auto *CategoryDecl = llvm::dyn_cast<ObjCCategoryDecl>(DeclContext);
113
114 if (CategoryDecl != nullptr &&
115 hasCategoryPropertyPrefix(MatchedDecl->getName())) {
116 if (!prefixedPropertyNameValid(MatchedDecl->getName()) ||
117 CategoryDecl->IsClassExtension()) {
118 NamingStyle Style = CategoryDecl->IsClassExtension() ? StandardProperty
119 : CategoryProperty;
120 diag(MatchedDecl->getLocation(),
121 "property name '%0' not using lowerCamelCase style or not prefixed "
122 "in a category, according to the Apple Coding Guidelines")
123 << MatchedDecl->getName() << generateFixItHint(MatchedDecl, Style);
124 }
125 return;
126 }
127 diag(MatchedDecl->getLocation(),
128 "property name '%0' not using lowerCamelCase style or not prefixed in "
129 "a category, according to the Apple Coding Guidelines")
130 << MatchedDecl->getName()
131 << generateFixItHint(MatchedDecl, StandardProperty);
132}
133
134} // 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)