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