152161a5aSBen Hamilton //===--- PropertyDeclarationCheck.cpp - clang-tidy-------------------------===//
252161a5aSBen Hamilton //
32946cd70SChandler Carruth // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
42946cd70SChandler Carruth // See https://llvm.org/LICENSE.txt for license information.
52946cd70SChandler Carruth // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
652161a5aSBen Hamilton //
752161a5aSBen Hamilton //===----------------------------------------------------------------------===//
852161a5aSBen Hamilton
952161a5aSBen Hamilton #include "PropertyDeclarationCheck.h"
1052161a5aSBen Hamilton #include "../utils/OptionsUtils.h"
1152161a5aSBen Hamilton #include "clang/AST/ASTContext.h"
1252161a5aSBen Hamilton #include "clang/ASTMatchers/ASTMatchFinder.h"
1375b3b541SYan Zhang #include "clang/Basic/CharInfo.h"
14ae5bc5aeSYan Zhang #include "llvm/ADT/STLExtras.h"
1552161a5aSBen Hamilton #include "llvm/ADT/StringExtras.h"
1652161a5aSBen Hamilton #include "llvm/Support/Regex.h"
17ab2d3ce4SAlexander Kornienko #include <algorithm>
1852161a5aSBen Hamilton
1952161a5aSBen Hamilton using namespace clang::ast_matchers;
2052161a5aSBen Hamilton
217d2ea6c4SCarlos Galvez namespace clang::tidy::objc {
2252161a5aSBen Hamilton
2352161a5aSBen Hamilton namespace {
2475b3b541SYan Zhang
2575b3b541SYan Zhang // For StandardProperty the naming style is 'lowerCamelCase'.
2675b3b541SYan Zhang // For CategoryProperty especially in categories of system class,
2775b3b541SYan Zhang // to avoid naming conflict, the suggested naming style is
2875b3b541SYan Zhang // 'abc_lowerCamelCase' (adding lowercase prefix followed by '_').
29af4755acSStephane Moore // Regardless of the style, all acronyms and initialisms should be capitalized.
3075b3b541SYan Zhang enum NamingStyle {
3175b3b541SYan Zhang StandardProperty = 1,
3275b3b541SYan Zhang CategoryProperty = 2,
3375b3b541SYan Zhang };
3475b3b541SYan Zhang
3575b3b541SYan Zhang /// For now we will only fix 'CamelCase' or 'abc_CamelCase' property to
3675b3b541SYan Zhang /// 'camelCase' or 'abc_camelCase'. For other cases the users need to
3752161a5aSBen Hamilton /// come up with a proper name by their own.
3852161a5aSBen Hamilton /// FIXME: provide fix for snake_case to snakeCase
generateFixItHint(const ObjCPropertyDecl * Decl,NamingStyle Style)3975b3b541SYan Zhang FixItHint generateFixItHint(const ObjCPropertyDecl *Decl, NamingStyle Style) {
4075b3b541SYan Zhang auto Name = Decl->getName();
4152161a5aSBen Hamilton auto NewName = Decl->getName().str();
4275b3b541SYan Zhang size_t Index = 0;
4375b3b541SYan Zhang if (Style == CategoryProperty) {
4475b3b541SYan Zhang Index = Name.find_first_of('_') + 1;
4575b3b541SYan Zhang NewName.replace(0, Index - 1, Name.substr(0, Index - 1).lower());
4675b3b541SYan Zhang }
4775b3b541SYan Zhang if (Index < Name.size()) {
4875b3b541SYan Zhang NewName[Index] = tolower(NewName[Index]);
4975b3b541SYan Zhang if (NewName != Name) {
5052161a5aSBen Hamilton return FixItHint::CreateReplacement(
5152161a5aSBen Hamilton CharSourceRange::getTokenRange(SourceRange(Decl->getLocation())),
5252161a5aSBen Hamilton llvm::StringRef(NewName));
5352161a5aSBen Hamilton }
5475b3b541SYan Zhang }
55*ec5f4be4SPiotr Zegar return {};
5652161a5aSBen Hamilton }
5752161a5aSBen Hamilton
validPropertyNameRegex(bool UsedInMatcher)58af4755acSStephane Moore std::string validPropertyNameRegex(bool UsedInMatcher) {
5952161a5aSBen Hamilton // Allow any of these names:
6052161a5aSBen Hamilton // foo
6152161a5aSBen Hamilton // fooBar
6252161a5aSBen Hamilton // url
6352161a5aSBen Hamilton // urlString
64af4755acSStephane Moore // ID
65af4755acSStephane Moore // IDs
6652161a5aSBen Hamilton // URL
6752161a5aSBen Hamilton // URLString
688c298d2fSYan Zhang // bundleID
69af4755acSStephane Moore // CIColor
70af4755acSStephane Moore //
71af4755acSStephane Moore // Disallow names of this form:
72af4755acSStephane Moore // LongString
73af4755acSStephane Moore //
74af4755acSStephane Moore // aRbITRaRyCapS is allowed to avoid generating false positives for names
75af4755acSStephane Moore // like isVitaminBSupplement, CProgrammingLanguage, and isBeforeM.
7675b3b541SYan Zhang std::string StartMatcher = UsedInMatcher ? "::" : "^";
77af4755acSStephane Moore return StartMatcher + "([a-z]|[A-Z][A-Z0-9])[a-z0-9A-Z]*$";
7875b3b541SYan Zhang }
7975b3b541SYan Zhang
hasCategoryPropertyPrefix(llvm::StringRef PropertyName)8075b3b541SYan Zhang bool hasCategoryPropertyPrefix(llvm::StringRef PropertyName) {
8119bceda8SYan Zhang auto RegexExp =
8219bceda8SYan Zhang llvm::Regex("^[a-zA-Z][a-zA-Z0-9]*_[a-zA-Z0-9][a-zA-Z0-9_]+$");
8375b3b541SYan Zhang return RegexExp.match(PropertyName);
8475b3b541SYan Zhang }
8575b3b541SYan Zhang
prefixedPropertyNameValid(llvm::StringRef PropertyName)86af4755acSStephane Moore bool prefixedPropertyNameValid(llvm::StringRef PropertyName) {
8775b3b541SYan Zhang size_t Start = PropertyName.find_first_of('_');
8875b3b541SYan Zhang assert(Start != llvm::StringRef::npos && Start + 1 < PropertyName.size());
8975b3b541SYan Zhang auto Prefix = PropertyName.substr(0, Start);
9075b3b541SYan Zhang if (Prefix.lower() != Prefix) {
9175b3b541SYan Zhang return false;
9275b3b541SYan Zhang }
9319bceda8SYan Zhang auto RegexExp = llvm::Regex(llvm::StringRef(validPropertyNameRegex(false)));
9475b3b541SYan Zhang return RegexExp.match(PropertyName.substr(Start + 1));
9552161a5aSBen Hamilton }
9652161a5aSBen Hamilton } // namespace
9752161a5aSBen Hamilton
registerMatchers(MatchFinder * Finder)9852161a5aSBen Hamilton void PropertyDeclarationCheck::registerMatchers(MatchFinder *Finder) {
9919bceda8SYan Zhang Finder->addMatcher(objcPropertyDecl(
10052161a5aSBen Hamilton // the property name should be in Lower Camel Case like
10152161a5aSBen Hamilton // 'lowerCamelCase'
102af4755acSStephane Moore unless(matchesName(validPropertyNameRegex(true))))
10352161a5aSBen Hamilton .bind("property"),
10452161a5aSBen Hamilton this);
10552161a5aSBen Hamilton }
10652161a5aSBen Hamilton
check(const MatchFinder::MatchResult & Result)10752161a5aSBen Hamilton void PropertyDeclarationCheck::check(const MatchFinder::MatchResult &Result) {
10852161a5aSBen Hamilton const auto *MatchedDecl =
10952161a5aSBen Hamilton Result.Nodes.getNodeAs<ObjCPropertyDecl>("property");
11052161a5aSBen Hamilton assert(MatchedDecl->getName().size() > 0);
11175b3b541SYan Zhang auto *DeclContext = MatchedDecl->getDeclContext();
11275b3b541SYan Zhang auto *CategoryDecl = llvm::dyn_cast<ObjCCategoryDecl>(DeclContext);
1134f9ead23SYan Zhang
11475b3b541SYan Zhang if (CategoryDecl != nullptr &&
11575b3b541SYan Zhang hasCategoryPropertyPrefix(MatchedDecl->getName())) {
116af4755acSStephane Moore if (!prefixedPropertyNameValid(MatchedDecl->getName()) ||
11775b3b541SYan Zhang CategoryDecl->IsClassExtension()) {
11875b3b541SYan Zhang NamingStyle Style = CategoryDecl->IsClassExtension() ? StandardProperty
11975b3b541SYan Zhang : CategoryProperty;
12052161a5aSBen Hamilton diag(MatchedDecl->getLocation(),
12175b3b541SYan Zhang "property name '%0' not using lowerCamelCase style or not prefixed "
12275b3b541SYan Zhang "in a category, according to the Apple Coding Guidelines")
12375b3b541SYan Zhang << MatchedDecl->getName() << generateFixItHint(MatchedDecl, Style);
12475b3b541SYan Zhang }
12575b3b541SYan Zhang return;
12675b3b541SYan Zhang }
12775b3b541SYan Zhang diag(MatchedDecl->getLocation(),
12875b3b541SYan Zhang "property name '%0' not using lowerCamelCase style or not prefixed in "
12975b3b541SYan Zhang "a category, according to the Apple Coding Guidelines")
13075b3b541SYan Zhang << MatchedDecl->getName()
13175b3b541SYan Zhang << generateFixItHint(MatchedDecl, StandardProperty);
13252161a5aSBen Hamilton }
13352161a5aSBen Hamilton
1347d2ea6c4SCarlos Galvez } // namespace clang::tidy::objc
135