1 | //===--- NamedParameterCheck.cpp - clang-tidy -------------------*- C++ -*-===// |
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 "NamedParameterCheck.h" |
10 | #include "clang/AST/ASTContext.h" |
11 | #include "clang/ASTMatchers/ASTMatchFinder.h" |
12 | #include "clang/ASTMatchers/ASTMatchers.h" |
13 | |
14 | using namespace clang::ast_matchers; |
15 | |
16 | namespace clang::tidy::readability { |
17 | |
18 | void NamedParameterCheck::registerMatchers(ast_matchers::MatchFinder *Finder) { |
19 | Finder->addMatcher(NodeMatch: functionDecl().bind(ID: "decl" ), Action: this); |
20 | } |
21 | |
22 | void NamedParameterCheck::check(const MatchFinder::MatchResult &Result) { |
23 | const SourceManager &SM = *Result.SourceManager; |
24 | const auto *Function = Result.Nodes.getNodeAs<FunctionDecl>(ID: "decl" ); |
25 | SmallVector<std::pair<const FunctionDecl *, unsigned>, 4> UnnamedParams; |
26 | |
27 | // Ignore declarations without a definition if we're not dealing with an |
28 | // overriden method. |
29 | const FunctionDecl *Definition = nullptr; |
30 | if ((!Function->isDefined(Definition) || Function->isDefaulted() || |
31 | Definition->isDefaulted() || Function->isDeleted()) && |
32 | (!isa<CXXMethodDecl>(Val: Function) || |
33 | cast<CXXMethodDecl>(Val: Function)->size_overridden_methods() == 0)) |
34 | return; |
35 | |
36 | // TODO: Handle overloads. |
37 | // TODO: We could check that all redeclarations use the same name for |
38 | // arguments in the same position. |
39 | for (unsigned I = 0, E = Function->getNumParams(); I != E; ++I) { |
40 | const ParmVarDecl *Parm = Function->getParamDecl(i: I); |
41 | if (Parm->isImplicit()) |
42 | continue; |
43 | // Look for unnamed parameters. |
44 | if (!Parm->getName().empty()) |
45 | continue; |
46 | |
47 | // Don't warn on the dummy argument on post-inc and post-dec operators. |
48 | if ((Function->getOverloadedOperator() == OO_PlusPlus || |
49 | Function->getOverloadedOperator() == OO_MinusMinus) && |
50 | Parm->getType()->isSpecificBuiltinType(BuiltinType::Int)) |
51 | continue; |
52 | |
53 | // Sanity check the source locations. |
54 | if (!Parm->getLocation().isValid() || Parm->getLocation().isMacroID() || |
55 | !SM.isWrittenInSameFile(Loc1: Parm->getBeginLoc(), Loc2: Parm->getLocation())) |
56 | continue; |
57 | |
58 | // Skip gmock testing::Unused parameters. |
59 | if (const auto *Typedef = Parm->getType()->getAs<clang::TypedefType>()) |
60 | if (Typedef->getDecl()->getQualifiedNameAsString() == "testing::Unused" ) |
61 | continue; |
62 | |
63 | // Skip std::nullptr_t. |
64 | if (Parm->getType().getCanonicalType()->isNullPtrType()) |
65 | continue; |
66 | |
67 | // Look for comments. We explicitly want to allow idioms like |
68 | // void foo(int /*unused*/) |
69 | const char *Begin = SM.getCharacterData(SL: Parm->getBeginLoc()); |
70 | const char *End = SM.getCharacterData(SL: Parm->getLocation()); |
71 | StringRef Data(Begin, End - Begin); |
72 | if (Data.contains(Other: "/*" )) |
73 | continue; |
74 | |
75 | UnnamedParams.push_back(Elt: std::make_pair(x&: Function, y&: I)); |
76 | } |
77 | |
78 | // Emit only one warning per function but fixits for all unnamed parameters. |
79 | if (!UnnamedParams.empty()) { |
80 | const ParmVarDecl *FirstParm = |
81 | UnnamedParams.front().first->getParamDecl(i: UnnamedParams.front().second); |
82 | auto D = diag(FirstParm->getLocation(), |
83 | "all parameters should be named in a function" ); |
84 | |
85 | for (auto P : UnnamedParams) { |
86 | // Fallback to an unused marker. |
87 | StringRef NewName = "unused" ; |
88 | |
89 | // If the method is overridden, try to copy the name from the base method |
90 | // into the overrider. |
91 | const auto *M = dyn_cast<CXXMethodDecl>(Val: P.first); |
92 | if (M && M->size_overridden_methods() > 0) { |
93 | const ParmVarDecl *OtherParm = |
94 | (*M->begin_overridden_methods())->getParamDecl(P.second); |
95 | StringRef Name = OtherParm->getName(); |
96 | if (!Name.empty()) |
97 | NewName = Name; |
98 | } |
99 | |
100 | // If the definition has a named parameter use that name. |
101 | if (Definition) { |
102 | const ParmVarDecl *DefParm = Definition->getParamDecl(i: P.second); |
103 | StringRef Name = DefParm->getName(); |
104 | if (!Name.empty()) |
105 | NewName = Name; |
106 | } |
107 | |
108 | // Now insert the comment. Note that getLocation() points to the place |
109 | // where the name would be, this allows us to also get complex cases like |
110 | // function pointers right. |
111 | const ParmVarDecl *Parm = P.first->getParamDecl(i: P.second); |
112 | D << FixItHint::CreateInsertion(InsertionLoc: Parm->getLocation(), |
113 | Code: " /*" + NewName.str() + "*/" ); |
114 | } |
115 | } |
116 | } |
117 | |
118 | } // namespace clang::tidy::readability |
119 | |