blob: 653c6bb86944bc2d07c95bca1b3b7cd03775e36f [file] [log] [blame]
Ben Hamilton52161a52017-11-13 23:54:31 +00001//===--- PropertyDeclarationCheck.cpp - clang-tidy-------------------------===//
2//
Chandler Carruth2946cd72019-01-19 08:50:56 +00003// 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
Ben Hamilton52161a52017-11-13 23:54:31 +00006//
7//===----------------------------------------------------------------------===//
8
9#include "PropertyDeclarationCheck.h"
Yan Zhang75b3b542018-01-30 01:44:00 +000010#include <algorithm>
Ben Hamilton52161a52017-11-13 23:54:31 +000011#include "../utils/OptionsUtils.h"
12#include "clang/AST/ASTContext.h"
13#include "clang/ASTMatchers/ASTMatchFinder.h"
Yan Zhang75b3b542018-01-30 01:44:00 +000014#include "clang/Basic/CharInfo.h"
Yan Zhangae5bc5ae2018-02-06 21:40:38 +000015#include "llvm/ADT/STLExtras.h"
Ben Hamilton52161a52017-11-13 23:54:31 +000016#include "llvm/ADT/StringExtras.h"
17#include "llvm/Support/Regex.h"
Ben Hamilton52161a52017-11-13 23:54:31 +000018
19using namespace clang::ast_matchers;
20
21namespace clang {
22namespace tidy {
23namespace objc {
24
25namespace {
Yan Zhang75b3b542018-01-30 01:44:00 +000026
27// For StandardProperty the naming style is 'lowerCamelCase'.
28// For CategoryProperty especially in categories of system class,
29// to avoid naming conflict, the suggested naming style is
30// 'abc_lowerCamelCase' (adding lowercase prefix followed by '_').
Stephane Mooreaf4755a2018-12-05 03:44:03 +000031// Regardless of the style, all acronyms and initialisms should be capitalized.
Yan Zhang75b3b542018-01-30 01:44:00 +000032enum NamingStyle {
33 StandardProperty = 1,
34 CategoryProperty = 2,
35};
36
Yan Zhang75b3b542018-01-30 01:44:00 +000037/// For now we will only fix 'CamelCase' or 'abc_CamelCase' property to
38/// 'camelCase' or 'abc_camelCase'. For other cases the users need to
Ben Hamilton52161a52017-11-13 23:54:31 +000039/// come up with a proper name by their own.
40/// FIXME: provide fix for snake_case to snakeCase
Yan Zhang75b3b542018-01-30 01:44:00 +000041FixItHint generateFixItHint(const ObjCPropertyDecl *Decl, NamingStyle Style) {
42 auto Name = Decl->getName();
43 auto NewName = Decl->getName().str();
44 size_t Index = 0;
45 if (Style == CategoryProperty) {
46 Index = Name.find_first_of('_') + 1;
47 NewName.replace(0, Index - 1, Name.substr(0, Index - 1).lower());
48 }
49 if (Index < Name.size()) {
50 NewName[Index] = tolower(NewName[Index]);
51 if (NewName != Name) {
52 return FixItHint::CreateReplacement(
53 CharSourceRange::getTokenRange(SourceRange(Decl->getLocation())),
54 llvm::StringRef(NewName));
55 }
Ben Hamilton52161a52017-11-13 23:54:31 +000056 }
57 return FixItHint();
58}
59
Stephane Mooreaf4755a2018-12-05 03:44:03 +000060std::string validPropertyNameRegex(bool UsedInMatcher) {
Ben Hamilton52161a52017-11-13 23:54:31 +000061 // Allow any of these names:
62 // foo
63 // fooBar
64 // url
65 // urlString
Stephane Mooreaf4755a2018-12-05 03:44:03 +000066 // ID
67 // IDs
Ben Hamilton52161a52017-11-13 23:54:31 +000068 // URL
69 // URLString
Yan Zhang8c298d22018-01-17 00:19:35 +000070 // bundleID
Stephane Mooreaf4755a2018-12-05 03:44:03 +000071 // CIColor
72 //
73 // Disallow names of this form:
74 // LongString
75 //
76 // aRbITRaRyCapS is allowed to avoid generating false positives for names
77 // like isVitaminBSupplement, CProgrammingLanguage, and isBeforeM.
Yan Zhang75b3b542018-01-30 01:44:00 +000078 std::string StartMatcher = UsedInMatcher ? "::" : "^";
Stephane Mooreaf4755a2018-12-05 03:44:03 +000079 return StartMatcher + "([a-z]|[A-Z][A-Z0-9])[a-z0-9A-Z]*$";
Yan Zhang75b3b542018-01-30 01:44:00 +000080}
81
82bool hasCategoryPropertyPrefix(llvm::StringRef PropertyName) {
83 auto RegexExp = llvm::Regex("^[a-zA-Z]+_[a-zA-Z0-9][a-zA-Z0-9_]+$");
84 return RegexExp.match(PropertyName);
85}
86
Stephane Mooreaf4755a2018-12-05 03:44:03 +000087bool prefixedPropertyNameValid(llvm::StringRef PropertyName) {
Yan Zhang75b3b542018-01-30 01:44:00 +000088 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 =
Stephane Mooreaf4755a2018-12-05 03:44:03 +000095 llvm::Regex(llvm::StringRef(validPropertyNameRegex(false)));
Yan Zhang75b3b542018-01-30 01:44:00 +000096 return RegexExp.match(PropertyName.substr(Start + 1));
Ben Hamilton52161a52017-11-13 23:54:31 +000097}
98} // namespace
99
100PropertyDeclarationCheck::PropertyDeclarationCheck(StringRef Name,
101 ClangTidyContext *Context)
102 : ClangTidyCheck(Name, Context),
Ben Hamiltonf94c10d2018-01-22 15:45:25 +0000103 SpecialAcronyms(
104 utils::options::parseStringList(Options.get("Acronyms", ""))),
Yan Zhang75b3b542018-01-30 01:44:00 +0000105 IncludeDefaultAcronyms(Options.get("IncludeDefaultAcronyms", true)),
106 EscapedAcronyms() {}
Ben Hamilton52161a52017-11-13 23:54:31 +0000107
108void PropertyDeclarationCheck::registerMatchers(MatchFinder *Finder) {
Yan Zhangc7faee72018-03-07 18:59:25 +0000109 // this check should only be applied to ObjC sources.
Yan Zhangcc1e9c42018-11-01 17:36:18 +0000110 if (!getLangOpts().ObjC) return;
Erik Pilkingtonfa983902018-10-30 20:31:30 +0000111
Ben Hamilton52161a52017-11-13 23:54:31 +0000112 Finder->addMatcher(
113 objcPropertyDecl(
114 // the property name should be in Lower Camel Case like
115 // 'lowerCamelCase'
Stephane Mooreaf4755a2018-12-05 03:44:03 +0000116 unless(matchesName(validPropertyNameRegex(true))))
Ben Hamilton52161a52017-11-13 23:54:31 +0000117 .bind("property"),
118 this);
119}
120
121void PropertyDeclarationCheck::check(const MatchFinder::MatchResult &Result) {
122 const auto *MatchedDecl =
123 Result.Nodes.getNodeAs<ObjCPropertyDecl>("property");
124 assert(MatchedDecl->getName().size() > 0);
Yan Zhang75b3b542018-01-30 01:44:00 +0000125 auto *DeclContext = MatchedDecl->getDeclContext();
126 auto *CategoryDecl = llvm::dyn_cast<ObjCCategoryDecl>(DeclContext);
Yan Zhang4f9ead22018-05-04 18:14:08 +0000127
Yan Zhang75b3b542018-01-30 01:44:00 +0000128 if (CategoryDecl != nullptr &&
129 hasCategoryPropertyPrefix(MatchedDecl->getName())) {
Stephane Mooreaf4755a2018-12-05 03:44:03 +0000130 if (!prefixedPropertyNameValid(MatchedDecl->getName()) ||
Yan Zhang75b3b542018-01-30 01:44:00 +0000131 CategoryDecl->IsClassExtension()) {
132 NamingStyle Style = CategoryDecl->IsClassExtension() ? StandardProperty
133 : CategoryProperty;
134 diag(MatchedDecl->getLocation(),
135 "property name '%0' not using lowerCamelCase style or not prefixed "
136 "in a category, according to the Apple Coding Guidelines")
137 << MatchedDecl->getName() << generateFixItHint(MatchedDecl, Style);
138 }
139 return;
140 }
Ben Hamilton52161a52017-11-13 23:54:31 +0000141 diag(MatchedDecl->getLocation(),
Yan Zhang75b3b542018-01-30 01:44:00 +0000142 "property name '%0' not using lowerCamelCase style or not prefixed in "
143 "a category, according to the Apple Coding Guidelines")
144 << MatchedDecl->getName()
145 << generateFixItHint(MatchedDecl, StandardProperty);
Ben Hamilton52161a52017-11-13 23:54:31 +0000146}
147
148void PropertyDeclarationCheck::storeOptions(ClangTidyOptions::OptionMap &Opts) {
149 Options.store(Opts, "Acronyms",
150 utils::options::serializeStringList(SpecialAcronyms));
Ben Hamiltonf94c10d2018-01-22 15:45:25 +0000151 Options.store(Opts, "IncludeDefaultAcronyms", IncludeDefaultAcronyms);
Ben Hamilton52161a52017-11-13 23:54:31 +0000152}
153
154} // namespace objc
155} // namespace tidy
156} // namespace clang