Index: clang-tools-extra/clang-tidy/google/CMakeLists.txt =================================================================== --- clang-tools-extra/clang-tidy/google/CMakeLists.txt +++ clang-tools-extra/clang-tidy/google/CMakeLists.txt @@ -15,6 +15,7 @@ IntegerTypesCheck.cpp NonConstReferences.cpp OverloadedUnaryAndCheck.cpp + RequireCategoryMethodPrefixesCheck.cpp TodoCommentCheck.cpp UnnamedNamespaceInHeaderCheck.cpp UpgradeGoogletestCaseCheck.cpp Index: clang-tools-extra/clang-tidy/google/GoogleTidyModule.cpp =================================================================== --- clang-tools-extra/clang-tidy/google/GoogleTidyModule.cpp +++ clang-tools-extra/clang-tidy/google/GoogleTidyModule.cpp @@ -25,6 +25,7 @@ #include "IntegerTypesCheck.h" #include "NonConstReferences.h" #include "OverloadedUnaryAndCheck.h" +#include "RequireCategoryMethodPrefixesCheck.h" #include "TodoCommentCheck.h" #include "UnnamedNamespaceInHeaderCheck.h" #include "UpgradeGoogletestCaseCheck.h" @@ -59,6 +60,8 @@ "google-objc-function-naming"); CheckFactories.registerCheck( "google-objc-global-variable-declaration"); + CheckFactories.registerCheck( + "google-objc-require-category-method-prefixes"); CheckFactories.registerCheck( "google-runtime-int"); CheckFactories.registerCheck( Index: clang-tools-extra/clang-tidy/google/RequireCategoryMethodPrefixesCheck.h =================================================================== --- /dev/null +++ clang-tools-extra/clang-tidy/google/RequireCategoryMethodPrefixesCheck.h @@ -0,0 +1,39 @@ +#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_GOOGLE_REQUIRECATEGORYMETHODPREFIXES_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_GOOGLE_REQUIRECATEGORYMETHODPREFIXES_H + +#include "../ClangTidyCheck.h" +#include "clang/ASTMatchers/ASTMatchFinder.h" + +namespace clang { +namespace tidy { +namespace google { +namespace objc { + +// Warn about missing prefixes in category method names. +class RequireCategoryMethodPrefixesCheck : public ClangTidyCheck { +public: + RequireCategoryMethodPrefixesCheck(StringRef Name, ClangTidyContext *Context) + : ClangTidyCheck(Name, Context), + WhitelistedPrefixes(Options.get("WhitelistedPrefixes", "")) {} + + /// Make configuration of checker discoverable. + void storeOptions(ClangTidyOptions::OptionMap &Opts) override; + + void registerMatchers(ast_matchers::MatchFinder *finder) override; + void check(const ast_matchers::MatchFinder::MatchResult &result) override; + +private: + /// Semicolon-separated list of whitelisted class prefixes. + /// Defaults to empty. + const std::string WhitelistedPrefixes; + + llvm::Optional + matchingWhitelistedPrefix(StringRef class_name); +}; + +} // namespace objc +} // namespace google +} // namespace tidy +} // namespace clang + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_GOOGLE_REQUIRECATEGORYMETHODPREFIXES_H Index: clang-tools-extra/clang-tidy/google/RequireCategoryMethodPrefixesCheck.cpp =================================================================== --- /dev/null +++ clang-tools-extra/clang-tidy/google/RequireCategoryMethodPrefixesCheck.cpp @@ -0,0 +1,104 @@ +#include "RequireCategoryMethodPrefixesCheck.h" + +#include "../ClangTidyOptions.h" +#include "../utils/OptionsUtils.h" +#include "clang/AST/ASTContext.h" +#include "clang/ASTMatchers/ASTMatchers.h" + +using namespace clang::ast_matchers; // NOLINT: Too many names. + +namespace clang { +namespace tidy { +namespace google { +namespace objc { + +namespace { +static const char *kCustomCategoryMethodIdentifier = "ThisIsACategoryMethod"; +} // anonymous namespace + +void RequireCategoryMethodPrefixesCheck::registerMatchers(MatchFinder *finder) { + // This check should be run only on Objective-C code. + if (!getLangOpts().ObjC) + return; + + finder->addMatcher(objcMethodDecl().bind(kCustomCategoryMethodIdentifier), + this); +} + +void RequireCategoryMethodPrefixesCheck::storeOptions( + ClangTidyOptions::OptionMap &Opts) { + Options.store(Opts, "WhitelistedPrefixes", WhitelistedPrefixes); +} + +llvm::Optional +RequireCategoryMethodPrefixesCheck::matchingWhitelistedPrefix( + StringRef class_name) { + std::vector PrefixArray = + utils::options::parseStringList(WhitelistedPrefixes); + + for (auto &Prefix : PrefixArray) { + if (class_name.startswith(Prefix)) { + return Prefix; + } + } + return llvm::None; +} + +void RequireCategoryMethodPrefixesCheck::check( + const MatchFinder::MatchResult &result) { + const clang::ObjCMethodDecl *method_declaration = + result.Nodes.getNodeAs(kCustomCategoryMethodIdentifier); + if (!method_declaration) + return; + + // We cannot safely use getName here, because it will often fail with + // the error "Name is not a simple identifier". + StringRef method_name = method_declaration->getNameAsString(); + const clang::ObjCInterfaceDecl *owning_objc_class_interface = + method_declaration->getClassInterface(); + if (!owning_objc_class_interface) + return; + + StringRef class_name = owning_objc_class_interface->getName(); + + const clang::ObjCCategoryDecl *OwningObjCCategory = + dyn_cast(method_declaration->getDeclContext()); + // Bail early if the method is not in a category. + if (!OwningObjCCategory || OwningObjCCategory->isInvalidDecl()) + return; + + // Bail early if the method is in a category on a class with a whitelisted + // prefix. + llvm::Optional MatchingPrefix = + matchingWhitelistedPrefix(class_name); + + if (MatchingPrefix.hasValue()) + return; + + // Check whether the method name has a proper prefix. + for (const char *pos = method_name.str().c_str(); pos && *pos; ++pos) { + // The characters a-z are allowed in a prefix. Go to the next + // character. + if (*pos >= 'a' && *pos <= 'z') + continue; + + // The underscore character (_) terminates the prefix. As long as it is + // not the last character in the name, the declaration passes. + if (*pos == '_' && *(pos + 1) != '\0') + return; + + // The name does not start with /[a-z]+_/. This declaration fails. + break; + } + // If the end of the string is reached or an illegal character appears + // before the terminating underscore, then the method name is not + // properly prefixed. Throw an error. + diag(method_declaration->getBeginLoc(), + "the category method %0 is not properly prefixed") + << method_declaration; +} + +} // namespace objc +} // namespace google +} // namespace tidy +} // namespace clang Index: clang-tools-extra/docs/ReleaseNotes.rst =================================================================== --- clang-tools-extra/docs/ReleaseNotes.rst +++ clang-tools-extra/docs/ReleaseNotes.rst @@ -67,6 +67,13 @@ Improvements to clang-tidy -------------------------- +- New :doc:`google-objc-require-category-method-prefixes + ` check. + + Warns when Objective-C category method names are not properly prefixed (e.g. + ``gmo_methodName``) unless the category is extending a class with a + (configurable) whitelisted prefix. + - New :doc:`bugprone-dynamic-static-initializers ` check. Index: clang-tools-extra/docs/clang-tidy/checks/google-objc-require-category-method-prefixes.rst =================================================================== --- /dev/null +++ clang-tools-extra/docs/clang-tidy/checks/google-objc-require-category-method-prefixes.rst @@ -0,0 +1,44 @@ +.. title:: clang-tidy - google-objc-require-category-method-prefixes + +google-objc-require-category-method-prefixes +=========================== + +Warns when Objective-C category method names are not properly prefixed (e.g. +``gmo_methodName``) unless the category is extending a class with a +(configurable) whitelisted prefix. + +The Google Objective-C style guide requires +`prefixes for methods http://go/objc-style#Category_Names`_ in categories on +classes that you don't control (for example, categories on Apple or third-party +framework classes or classes created by other teams) to prevent name collisions +when those frameworks are updated. + +This checker ensures that all methods in categories have some sort of prefix +(e.g. ``gmo_``). It excludes categories on classes whose names have a +whitelisted three-letter prefix. + +For example, the following code sample is a properly prefixed method on a +non-owned class (``NSObject``): + +.. code-block:: objc + @interface NSObject (QEDMyCategory) + - (BOOL)qed_myCustomMethod; + @end + +If you whitelist the ``QED`` three-letter prefix, the following code sample +is also allowed: + +.. code-block:: objc + + @interface QEDMyClass (MyCategory) + - (BOOL)myCustomMethod; + @end + +Options +------- + +.. option:: WhitelistedPrefixes + + A semicolon-delimited list of class name prefixes. Methods in categories + that extend classes whose names begin with any of these prefixes are exempt + from the method name prefixing requirement. Index: clang-tools-extra/docs/clang-tidy/checks/list.rst =================================================================== --- clang-tools-extra/docs/clang-tidy/checks/list.rst +++ clang-tools-extra/docs/clang-tidy/checks/list.rst @@ -230,6 +230,7 @@ google-objc-avoid-throwing-exception google-objc-function-naming google-objc-global-variable-declaration + google-objc-require-category-method-prefixes google-readability-avoid-underscore-in-googletest-name google-readability-braces-around-statements (redirects to readability-braces-around-statements) google-readability-casting Index: clang-tools-extra/test/clang-tidy/google-objc-require-category-method-prefixes.m =================================================================== --- /dev/null +++ clang-tools-extra/test/clang-tidy/google-objc-require-category-method-prefixes.m @@ -0,0 +1,35 @@ +// RUN: %check_clang_tidy %s google-objc-require-category-method-prefixes %t -config="{CheckOptions: [{key: google-objc-require-category-method-prefixes.WhitelistedPrefixes, value: GMO}]}" + +@class NSString; + +@interface NSURL ++ (nullable instancetype)URLWithString:(NSString *)URLString; ++ (instancetype)alloc; +- (instancetype)init; +@end + +@interface NSURL (CustomExtension) + +- (void)unprefixedMethod; +- (void)unprefixed; +- (void)justprefixed_; + +@end + +// CHECK-MESSAGES: :[[@LINE-6]]:1: warning: the category method 'unprefixedMethod' is not properly prefixed [google-objc-require-category-method-prefixes] +// CHECK-MESSAGES: :[[@LINE-6]]:1: warning: the category method 'unprefixed' is not properly prefixed [google-objc-require-category-method-prefixes] +// CHECK-MESSAGES: :[[@LINE-6]]:1: warning: the category method 'justprefixed_' is not properly prefixed [google-objc-require-category-method-prefixes] + +@interface NSURL (CustomExtension2) +- (void)gmo_prefixedMethod; +@end + +@interface GMOURL ++ (nullable instancetype)URLWithString:(NSString *)URLString; ++ (instancetype)alloc; +- (instancetype)init; +@end + +@interface GMOURL (CustomExtension3) +- (void)unprefixedMethodInWhitelistedClass; +@end