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