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 | |
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. |
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(c: 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(pos: Index, n: 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 | RemoveRange: CharSourceRange::getTokenRange(R: SourceRange(Decl->getLocation())), |
81 | Code: llvm::StringRef(NewName)); |
82 | |
83 | return {}; |
84 | } |
85 | |
86 | } // namespace |
87 | |
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 | NodeMatch: functionDecl( |
97 | unless(anyOf(isExpansionInSystemHeader(), cxxMethodDecl(), |
98 | hasAncestor(namespaceDecl()), isMain(), isImplicit(), |
99 | matchesName(RegExp: validFunctionNameRegex(RequirePrefix: true)), |
100 | allOf(isStaticStorageClass(), |
101 | matchesName(RegExp: validFunctionNameRegex(RequirePrefix: false)))))) |
102 | .bind(ID: "function" ), |
103 | Action: this); |
104 | } |
105 | |
106 | void FunctionNamingCheck::check(const MatchFinder::MatchResult &Result) { |
107 | const auto *MatchedDecl = Result.Nodes.getNodeAs<FunctionDecl>(ID: "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(Decl: MatchedDecl); |
115 | } |
116 | |
117 | } // namespace clang::tidy::google::objc |
118 | |