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