PropertyDeclarationCheck.cpp 4.7 KB

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