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

source code of clang-tools-extra/clang-tidy/objc/PropertyDeclarationCheck.cpp