1e34a761dSStephane Moore //===--- FunctionNamingCheck.cpp - clang-tidy -----------------------------===//
2e34a761dSStephane Moore //
32946cd70SChandler Carruth // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
42946cd70SChandler Carruth // See https://llvm.org/LICENSE.txt for license information.
52946cd70SChandler Carruth // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6e34a761dSStephane Moore //
7e34a761dSStephane Moore //===----------------------------------------------------------------------===//
8e34a761dSStephane Moore
9e34a761dSStephane Moore #include "FunctionNamingCheck.h"
10e34a761dSStephane Moore #include "clang/AST/ASTContext.h"
11e34a761dSStephane Moore #include "clang/ASTMatchers/ASTMatchFinder.h"
12e34a761dSStephane Moore #include "llvm/Support/Regex.h"
13e34a761dSStephane Moore
14e34a761dSStephane Moore using namespace clang::ast_matchers;
15e34a761dSStephane Moore
167d2ea6c4SCarlos Galvez namespace clang::tidy::google::objc {
17e34a761dSStephane Moore
18e34a761dSStephane Moore namespace {
19e34a761dSStephane Moore
validFunctionNameRegex(bool RequirePrefix)20e34a761dSStephane Moore std::string validFunctionNameRegex(bool RequirePrefix) {
21e34a761dSStephane Moore // Allow the following name patterns for all functions:
22e34a761dSStephane Moore // • ABFoo (prefix + UpperCamelCase)
23e34a761dSStephane Moore // • ABURL (prefix + capitalized acronym/initialism)
24e34a761dSStephane Moore //
25e34a761dSStephane Moore // If no prefix is required, additionally allow the following name patterns:
26e34a761dSStephane Moore // • Foo (UpperCamelCase)
27e34a761dSStephane Moore // • URL (capitalized acronym/initialism)
28e34a761dSStephane Moore //
29e34a761dSStephane Moore // The function name following the prefix can contain standard and
30e34a761dSStephane Moore // non-standard capitalized character sequences including acronyms,
31e34a761dSStephane Moore // initialisms, and prefixes of symbols (e.g., UIColorFromNSString). For this
32e34a761dSStephane Moore // reason, the regex only verifies that the function name after the prefix
33e34a761dSStephane Moore // begins with a capital letter followed by an arbitrary sequence of
34e34a761dSStephane Moore // alphanumeric characters.
35e34a761dSStephane Moore //
36e34a761dSStephane Moore // If a prefix is required, the regex checks for a capital letter followed by
37e34a761dSStephane Moore // another capital letter or number that is part of the prefix and another
38e34a761dSStephane Moore // capital letter or number that begins the name following the prefix.
39e34a761dSStephane Moore std::string FunctionNameMatcher =
40e34a761dSStephane Moore std::string(RequirePrefix ? "[A-Z][A-Z0-9]+" : "") + "[A-Z][a-zA-Z0-9]*";
41e34a761dSStephane Moore return std::string("::(") + FunctionNameMatcher + ")$";
42e34a761dSStephane Moore }
43e34a761dSStephane Moore
44e34a761dSStephane Moore /// For now we will only fix functions of static storage class with names like
45e34a761dSStephane Moore /// 'functionName' or 'function_name' and convert them to 'FunctionName'. For
46e34a761dSStephane Moore /// other cases the user must determine an appropriate name on their own.
generateFixItHint(const FunctionDecl * Decl)47e34a761dSStephane Moore FixItHint generateFixItHint(const FunctionDecl *Decl) {
48e34a761dSStephane Moore // A fixit can be generated for functions of static storage class but
49e34a761dSStephane Moore // otherwise the check cannot determine the appropriate function name prefix
50e34a761dSStephane Moore // to use.
512fd11e0bSThorsten Schütt if (Decl->getStorageClass() != SC_Static)
52*ec5f4be4SPiotr Zegar return {};
53e34a761dSStephane Moore
54e34a761dSStephane Moore StringRef Name = Decl->getName();
55e34a761dSStephane Moore std::string NewName = Decl->getName().str();
56e34a761dSStephane Moore
57e34a761dSStephane Moore size_t Index = 0;
58e34a761dSStephane Moore bool AtWordBoundary = true;
59e34a761dSStephane Moore while (Index < NewName.size()) {
60ab2d3ce4SAlexander Kornienko char Ch = NewName[Index];
61ab2d3ce4SAlexander Kornienko if (isalnum(Ch)) {
62e34a761dSStephane Moore // Capitalize the first letter after every word boundary.
63e34a761dSStephane Moore if (AtWordBoundary) {
64e34a761dSStephane Moore NewName[Index] = toupper(NewName[Index]);
65e34a761dSStephane Moore AtWordBoundary = false;
66e34a761dSStephane Moore }
67e34a761dSStephane Moore
68e34a761dSStephane Moore // Advance the index after every alphanumeric character.
69e34a761dSStephane Moore Index++;
70e34a761dSStephane Moore } else {
71e34a761dSStephane Moore // Strip out any characters other than alphanumeric characters.
72e34a761dSStephane Moore NewName.erase(Index, 1);
73e34a761dSStephane Moore AtWordBoundary = true;
74e34a761dSStephane Moore }
75e34a761dSStephane Moore }
76e34a761dSStephane Moore
77e34a761dSStephane Moore // Generate a fixit hint if the new name is different.
78e34a761dSStephane Moore if (NewName != Name)
79e34a761dSStephane Moore return FixItHint::CreateReplacement(
80e34a761dSStephane Moore CharSourceRange::getTokenRange(SourceRange(Decl->getLocation())),
81e34a761dSStephane Moore llvm::StringRef(NewName));
82e34a761dSStephane Moore
83*ec5f4be4SPiotr Zegar return {};
84e34a761dSStephane Moore }
85e34a761dSStephane Moore
86e34a761dSStephane Moore } // namespace
87e34a761dSStephane Moore
registerMatchers(MatchFinder * Finder)88e34a761dSStephane Moore void FunctionNamingCheck::registerMatchers(MatchFinder *Finder) {
893eea706eSStephane Moore // Enforce Objective-C function naming conventions on all functions except:
903eea706eSStephane Moore // • Functions defined in system headers.
913eea706eSStephane Moore // • C++ member functions.
923eea706eSStephane Moore // • Namespaced functions.
933eea706eSStephane Moore // • Implicitly defined functions.
943eea706eSStephane Moore // • The main function.
95e34a761dSStephane Moore Finder->addMatcher(
96e34a761dSStephane Moore functionDecl(
97f6d96e0fSStephane Moore unless(anyOf(isExpansionInSystemHeader(), cxxMethodDecl(),
983eea706eSStephane Moore hasAncestor(namespaceDecl()), isMain(), isImplicit(),
99f6d96e0fSStephane Moore matchesName(validFunctionNameRegex(true)),
100e34a761dSStephane Moore allOf(isStaticStorageClass(),
101e34a761dSStephane Moore matchesName(validFunctionNameRegex(false))))))
102e34a761dSStephane Moore .bind("function"),
103e34a761dSStephane Moore this);
104e34a761dSStephane Moore }
105e34a761dSStephane Moore
check(const MatchFinder::MatchResult & Result)106e34a761dSStephane Moore void FunctionNamingCheck::check(const MatchFinder::MatchResult &Result) {
107e34a761dSStephane Moore const auto *MatchedDecl = Result.Nodes.getNodeAs<FunctionDecl>("function");
108e34a761dSStephane Moore
1092fd11e0bSThorsten Schütt bool IsGlobal = MatchedDecl->getStorageClass() != SC_Static;
110e34a761dSStephane Moore diag(MatchedDecl->getLocation(),
111b69ece89SStephane Moore "%select{static function|function in global namespace}1 named %0 must "
112b69ece89SStephane Moore "%select{be in|have an appropriate prefix followed by}1 Pascal case as "
113b69ece89SStephane Moore "required by Google Objective-C style guide")
114b69ece89SStephane Moore << MatchedDecl << IsGlobal << generateFixItHint(MatchedDecl);
115e34a761dSStephane Moore }
116e34a761dSStephane Moore
1177d2ea6c4SCarlos Galvez } // namespace clang::tidy::google::objc
118