forked from OSchip/llvm-project
263 lines
7.7 KiB
C++
263 lines
7.7 KiB
C++
//===--- PropertyDeclarationCheck.cpp - clang-tidy-------------------------===//
|
|
//
|
|
// The LLVM Compiler Infrastructure
|
|
//
|
|
// This file is distributed under the University of Illinois Open Source
|
|
// License. See LICENSE.TXT for details.
|
|
//
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
#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 '_').
|
|
enum NamingStyle {
|
|
StandardProperty = 1,
|
|
CategoryProperty = 2,
|
|
};
|
|
|
|
/// The acronyms are aggregated from multiple sources including
|
|
/// https://developer.apple.com/library/content/documentation/Cocoa/Conceptual/CodingGuidelines/Articles/APIAbbreviations.html#//apple_ref/doc/uid/20001285-BCIHCGAE
|
|
///
|
|
/// Keep this list sorted.
|
|
constexpr llvm::StringLiteral DefaultSpecialAcronyms[] = {
|
|
"[2-9]G",
|
|
"ACL",
|
|
"API",
|
|
"AR",
|
|
"ARGB",
|
|
"ASCII",
|
|
"AV",
|
|
"BGRA",
|
|
"CA",
|
|
"CF",
|
|
"CG",
|
|
"CI",
|
|
"CRC",
|
|
"CV",
|
|
"CMYK",
|
|
"DNS",
|
|
"FPS",
|
|
"FTP",
|
|
"GIF",
|
|
"GL",
|
|
"GPS",
|
|
"GUID",
|
|
"HD",
|
|
"HDR",
|
|
"HMAC",
|
|
"HTML",
|
|
"HTTP",
|
|
"HTTPS",
|
|
"HUD",
|
|
"ID",
|
|
"JPG",
|
|
"JS",
|
|
"LAN",
|
|
"LZW",
|
|
"MAC",
|
|
"MD",
|
|
"MDNS",
|
|
"MIDI",
|
|
"NS",
|
|
"OS",
|
|
"PDF",
|
|
"PIN",
|
|
"PNG",
|
|
"POI",
|
|
"PSTN",
|
|
"PTR",
|
|
"QA",
|
|
"QOS",
|
|
"RGB",
|
|
"RGBA",
|
|
"RGBX",
|
|
"RIPEMD",
|
|
"ROM",
|
|
"RPC",
|
|
"RTF",
|
|
"RTL",
|
|
"SC",
|
|
"SDK",
|
|
"SHA",
|
|
"SSO",
|
|
"TCP",
|
|
"TIFF",
|
|
"TTS",
|
|
"UI",
|
|
"URI",
|
|
"URL",
|
|
"UUID",
|
|
"VC",
|
|
"VOIP",
|
|
"VPN",
|
|
"VR",
|
|
"W",
|
|
"WAN",
|
|
"X",
|
|
"XML",
|
|
"Y",
|
|
"Z",
|
|
};
|
|
|
|
/// 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 AcronymsGroupRegex(llvm::ArrayRef<std::string> EscapedAcronyms) {
|
|
return "(" +
|
|
llvm::join(EscapedAcronyms.begin(), EscapedAcronyms.end(), "s?|") +
|
|
"s?)";
|
|
}
|
|
|
|
std::string validPropertyNameRegex(llvm::ArrayRef<std::string> EscapedAcronyms,
|
|
bool UsedInMatcher) {
|
|
// Allow any of these names:
|
|
// foo
|
|
// fooBar
|
|
// url
|
|
// urlString
|
|
// URL
|
|
// URLString
|
|
// bundleID
|
|
std::string StartMatcher = UsedInMatcher ? "::" : "^";
|
|
std::string AcronymsMatcher = AcronymsGroupRegex(EscapedAcronyms);
|
|
return StartMatcher + "(" + AcronymsMatcher + "[A-Z]?)?[a-z]+[a-z0-9]*(" +
|
|
AcronymsMatcher + "|([A-Z][a-z0-9]+)|A|I)*$";
|
|
}
|
|
|
|
bool hasCategoryPropertyPrefix(llvm::StringRef PropertyName) {
|
|
auto RegexExp = llvm::Regex("^[a-zA-Z]+_[a-zA-Z0-9][a-zA-Z0-9_]+$");
|
|
return RegexExp.match(PropertyName);
|
|
}
|
|
|
|
bool prefixedPropertyNameValid(llvm::StringRef PropertyName,
|
|
llvm::ArrayRef<std::string> Acronyms) {
|
|
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(Acronyms, false)));
|
|
return RegexExp.match(PropertyName.substr(Start + 1));
|
|
}
|
|
} // namespace
|
|
|
|
PropertyDeclarationCheck::PropertyDeclarationCheck(StringRef Name,
|
|
ClangTidyContext *Context)
|
|
: ClangTidyCheck(Name, Context),
|
|
SpecialAcronyms(
|
|
utils::options::parseStringList(Options.get("Acronyms", ""))),
|
|
IncludeDefaultAcronyms(Options.get("IncludeDefaultAcronyms", true)),
|
|
EscapedAcronyms() {}
|
|
|
|
void PropertyDeclarationCheck::registerMatchers(MatchFinder *Finder) {
|
|
// this check should only be applied to ObjC sources.
|
|
if (!getLangOpts().ObjC1 && !getLangOpts().ObjC2) {
|
|
return;
|
|
}
|
|
if (IncludeDefaultAcronyms) {
|
|
EscapedAcronyms.reserve(llvm::array_lengthof(DefaultSpecialAcronyms) +
|
|
SpecialAcronyms.size());
|
|
// No need to regex-escape the default acronyms.
|
|
EscapedAcronyms.insert(EscapedAcronyms.end(),
|
|
std::begin(DefaultSpecialAcronyms),
|
|
std::end(DefaultSpecialAcronyms));
|
|
} else {
|
|
EscapedAcronyms.reserve(SpecialAcronyms.size());
|
|
}
|
|
// In case someone defines a prefix which includes a regex
|
|
// special character, regex-escape all the user-defined prefixes.
|
|
std::transform(SpecialAcronyms.begin(), SpecialAcronyms.end(),
|
|
std::back_inserter(EscapedAcronyms),
|
|
[](const std::string &s) { return llvm::Regex::escape(s); });
|
|
Finder->addMatcher(
|
|
objcPropertyDecl(
|
|
// the property name should be in Lower Camel Case like
|
|
// 'lowerCamelCase'
|
|
unless(matchesName(validPropertyNameRegex(EscapedAcronyms, 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);
|
|
|
|
auto AcronymsRegex =
|
|
llvm::Regex("^" + AcronymsGroupRegex(EscapedAcronyms) + "$");
|
|
if (AcronymsRegex.match(MatchedDecl->getName())) {
|
|
return;
|
|
}
|
|
if (CategoryDecl != nullptr &&
|
|
hasCategoryPropertyPrefix(MatchedDecl->getName())) {
|
|
if (!prefixedPropertyNameValid(MatchedDecl->getName(), EscapedAcronyms) ||
|
|
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);
|
|
}
|
|
|
|
void PropertyDeclarationCheck::storeOptions(ClangTidyOptions::OptionMap &Opts) {
|
|
Options.store(Opts, "Acronyms",
|
|
utils::options::serializeStringList(SpecialAcronyms));
|
|
Options.store(Opts, "IncludeDefaultAcronyms", IncludeDefaultAcronyms);
|
|
}
|
|
|
|
} // namespace objc
|
|
} // namespace tidy
|
|
} // namespace clang
|