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,38 @@ +#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), + ExemptClassPrefixes(Options.get("ExemptClassPrefixes", "")) {} + + /// 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 ExemptClassPrefixes; + + bool matchesExemptClassPrefix(StringRef ClassName); +}; + +} // 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,98 @@ +#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 { + +static const char *CustomCategoryMethodIdentifier = "ThisIsACategoryMethod"; + +void RequireCategoryMethodPrefixesCheck::registerMatchers(MatchFinder *Finder) { + // This check should be run only on Objective-C code. + if (!getLangOpts().ObjC) + return; + + Finder->addMatcher(objcMethodDecl(hasDeclContext( + objcCategoryDecl())).bind(CustomCategoryMethodIdentifier), this); +} + +void RequireCategoryMethodPrefixesCheck::storeOptions( + ClangTidyOptions::OptionMap &Opts) { + Options.store(Opts, "ExemptClassPrefixes", ExemptClassPrefixes); +} + +bool RequireCategoryMethodPrefixesCheck::matchesExemptClassPrefix( + StringRef ClassName) { + std::vector PrefixArray = + utils::options::parseStringList(ExemptClassPrefixes); + + return llvm::any_of(PrefixArray, + [ClassName](const std::string &Str) { + return ClassName.startswith(Str); + }); +} + +void RequireCategoryMethodPrefixesCheck::check( + const MatchFinder::MatchResult &Result) { + const ObjCMethodDecl *MethodDeclaration = + Result.Nodes.getNodeAs(CustomCategoryMethodIdentifier); + if (!MethodDeclaration) + return; + + // We cannot use getName or getIdentifier on Objective-C methods, because + // they crash or return NULL, so get the selector and then convert that to + // a string instead. + std::string MethodName = + MethodDeclaration->getDeclName().getObjCSelector().getAsString(); + const ObjCInterfaceDecl *OwningObjCClassInterface = + MethodDeclaration->getClassInterface(); + if (!OwningObjCClassInterface) + return; + + StringRef ClassName = OwningObjCClassInterface->getName(); + + const auto *OwningObjCCategory = + dyn_cast(MethodDeclaration->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. + if (matchesExemptClassPrefix(ClassName)) + return; + + // Check whether the method name has a proper prefix. + for (const char *pos = MethodName.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(MethodDeclaration->getBeginLoc(), + "the category method %0 is not properly prefixed") + << MethodDeclaration; +} + +} // 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 @@ -73,11 +73,12 @@ Finds instances where variables with static storage are initialized dynamically in header files. -- New :doc:`linuxkernel-must-use-errs - ` check. +- New :doc:`google-objc-require-category-method-prefixes + ` check. - Checks Linux kernel code to see if it uses the results from the functions in - ``linux/err.h``. + 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:`google-upgrade-googletest-case ` check. @@ -85,6 +86,12 @@ Finds uses of deprecated Googletest APIs with names containing ``case`` and replaces them with equivalent APIs with ``suite``. +- New :doc:`linuxkernel-must-use-errs + ` check. + + Checks Linux kernel code to see if it uses the results from the functions in + ``linux/err.h``. + - New :doc:`llvm-prefer-register-over-unsigned ` 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,45 @@ +.. 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 an +expected prefix (configurable). + +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 allows you to provide a list of expected three-letter +prefixes specific to your project, and ignores non-prefixed methods in +categories on classes whose names start with any of those prefixes. + +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 specify ``QED`` as an expected three-letter prefix, the following code +sample is also allowed: + +.. code-block:: objc + + @interface QEDMyClass (MyCategory) + - (BOOL)myCustomMethod; + @end + +Options +------- + +.. option:: ExemptClassPrefixes + + 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.ExemptClassPrefixes, 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)unprefixedMethodInClassWithExpectedPrefix; +@end