1//===--- InconsistentDeclarationParameterNameCheck.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 "InconsistentDeclarationParameterNameCheck.h"
10#include "clang/ASTMatchers/ASTMatchFinder.h"
11#include "llvm/ADT/STLExtras.h"
12
13using namespace clang::ast_matchers;
14
15namespace clang::tidy::readability {
16
17namespace {
18
19AST_MATCHER(FunctionDecl, hasOtherDeclarations) {
20 auto It = Node.redecls_begin();
21 auto EndIt = Node.redecls_end();
22
23 if (It == EndIt)
24 return false;
25
26 ++It;
27 return It != EndIt;
28}
29
30struct DifferingParamInfo {
31 DifferingParamInfo(StringRef SourceName, StringRef OtherName,
32 SourceRange OtherNameRange, bool GenerateFixItHint)
33 : SourceName(SourceName), OtherName(OtherName),
34 OtherNameRange(OtherNameRange), GenerateFixItHint(GenerateFixItHint) {}
35
36 StringRef SourceName;
37 StringRef OtherName;
38 SourceRange OtherNameRange;
39 bool GenerateFixItHint;
40};
41
42using DifferingParamsContainer = llvm::SmallVector<DifferingParamInfo, 10>;
43
44struct InconsistentDeclarationInfo {
45 InconsistentDeclarationInfo(SourceLocation DeclarationLocation,
46 DifferingParamsContainer &&DifferingParams)
47 : DeclarationLocation(DeclarationLocation),
48 DifferingParams(std::move(DifferingParams)) {}
49
50 SourceLocation DeclarationLocation;
51 DifferingParamsContainer DifferingParams;
52};
53
54using InconsistentDeclarationsContainer =
55 llvm::SmallVector<InconsistentDeclarationInfo, 2>;
56
57bool checkIfFixItHintIsApplicable(
58 const FunctionDecl *ParameterSourceDeclaration,
59 const ParmVarDecl *SourceParam, const FunctionDecl *OriginalDeclaration) {
60 // Assumptions with regard to function declarations/definition:
61 // * If both function declaration and definition are seen, assume that
62 // definition is most up-to-date, and use it to generate replacements.
63 // * If only function declarations are seen, there is no easy way to tell
64 // which is up-to-date and which is not, so don't do anything.
65 // TODO: This may be changed later, but for now it seems the reasonable
66 // solution.
67 if (!ParameterSourceDeclaration->isThisDeclarationADefinition())
68 return false;
69
70 // Assumption: if parameter is not referenced in function definition body, it
71 // may indicate that it's outdated, so don't touch it.
72 if (!SourceParam->isReferenced())
73 return false;
74
75 // In case there is the primary template definition and (possibly several)
76 // template specializations (and each with possibly several redeclarations),
77 // it is not at all clear what to change.
78 if (OriginalDeclaration->getTemplatedKind() ==
79 FunctionDecl::TK_FunctionTemplateSpecialization)
80 return false;
81
82 // Other cases seem OK to allow replacements.
83 return true;
84}
85
86bool nameMatch(StringRef L, StringRef R, bool Strict) {
87 if (Strict)
88 return L.empty() || R.empty() || L == R;
89 // We allow two names if one is a prefix/suffix of the other, ignoring case.
90 // Important special case: this is true if either parameter has no name!
91 return L.starts_with_insensitive(Prefix: R) || R.starts_with_insensitive(Prefix: L) ||
92 L.ends_with_insensitive(Suffix: R) || R.ends_with_insensitive(Suffix: L);
93}
94
95DifferingParamsContainer
96findDifferingParamsInDeclaration(const FunctionDecl *ParameterSourceDeclaration,
97 const FunctionDecl *OtherDeclaration,
98 const FunctionDecl *OriginalDeclaration,
99 bool Strict) {
100 DifferingParamsContainer DifferingParams;
101
102 const auto *SourceParamIt = ParameterSourceDeclaration->param_begin();
103 const auto *OtherParamIt = OtherDeclaration->param_begin();
104
105 while (SourceParamIt != ParameterSourceDeclaration->param_end() &&
106 OtherParamIt != OtherDeclaration->param_end()) {
107 auto SourceParamName = (*SourceParamIt)->getName();
108 auto OtherParamName = (*OtherParamIt)->getName();
109
110 // FIXME: Provide a way to extract commented out parameter name from comment
111 // next to it.
112 if (!nameMatch(SourceParamName, OtherParamName, Strict)) {
113 SourceRange OtherParamNameRange =
114 DeclarationNameInfo((*OtherParamIt)->getDeclName(),
115 (*OtherParamIt)->getLocation())
116 .getSourceRange();
117
118 bool GenerateFixItHint = checkIfFixItHintIsApplicable(
119 ParameterSourceDeclaration, SourceParam: *SourceParamIt, OriginalDeclaration);
120
121 DifferingParams.emplace_back(SourceParamName, OtherParamName,
122 OtherParamNameRange, GenerateFixItHint);
123 }
124
125 ++SourceParamIt;
126 ++OtherParamIt;
127 }
128
129 return DifferingParams;
130}
131
132InconsistentDeclarationsContainer
133findInconsistentDeclarations(const FunctionDecl *OriginalDeclaration,
134 const FunctionDecl *ParameterSourceDeclaration,
135 SourceManager &SM, bool Strict) {
136 InconsistentDeclarationsContainer InconsistentDeclarations;
137 SourceLocation ParameterSourceLocation =
138 ParameterSourceDeclaration->getLocation();
139
140 for (const FunctionDecl *OtherDeclaration : OriginalDeclaration->redecls()) {
141 SourceLocation OtherLocation = OtherDeclaration->getLocation();
142 if (OtherLocation != ParameterSourceLocation) { // Skip self.
143 DifferingParamsContainer DifferingParams =
144 findDifferingParamsInDeclaration(ParameterSourceDeclaration,
145 OtherDeclaration,
146 OriginalDeclaration, Strict);
147 if (!DifferingParams.empty()) {
148 InconsistentDeclarations.emplace_back(OtherDeclaration->getLocation(),
149 std::move(DifferingParams));
150 }
151 }
152 }
153
154 // Sort in order of appearance in translation unit to generate clear
155 // diagnostics.
156 llvm::sort(C&: InconsistentDeclarations,
157 Comp: [&SM](const InconsistentDeclarationInfo &Info1,
158 const InconsistentDeclarationInfo &Info2) {
159 return SM.isBeforeInTranslationUnit(LHS: Info1.DeclarationLocation,
160 RHS: Info2.DeclarationLocation);
161 });
162 return InconsistentDeclarations;
163}
164
165const FunctionDecl *
166getParameterSourceDeclaration(const FunctionDecl *OriginalDeclaration) {
167 const FunctionTemplateDecl *PrimaryTemplate =
168 OriginalDeclaration->getPrimaryTemplate();
169 if (PrimaryTemplate != nullptr) {
170 // In case of template specializations, use primary template declaration as
171 // the source of parameter names.
172 return PrimaryTemplate->getTemplatedDecl();
173 }
174
175 // In other cases, try to change to function definition, if available.
176
177 if (OriginalDeclaration->isThisDeclarationADefinition())
178 return OriginalDeclaration;
179
180 for (const FunctionDecl *OtherDeclaration : OriginalDeclaration->redecls()) {
181 if (OtherDeclaration->isThisDeclarationADefinition()) {
182 return OtherDeclaration;
183 }
184 }
185
186 // No definition found, so return original declaration.
187 return OriginalDeclaration;
188}
189
190std::string joinParameterNames(
191 const DifferingParamsContainer &DifferingParams,
192 llvm::function_ref<StringRef(const DifferingParamInfo &)> ChooseParamName) {
193 llvm::SmallString<40> Str;
194 bool First = true;
195 for (const DifferingParamInfo &ParamInfo : DifferingParams) {
196 if (First)
197 First = false;
198 else
199 Str += ", ";
200 Str.append(Refs: {"'", ChooseParamName(ParamInfo), "'"});
201 }
202 return std::string(Str);
203}
204
205void formatDifferingParamsDiagnostic(
206 InconsistentDeclarationParameterNameCheck *Check, SourceLocation Location,
207 StringRef OtherDeclarationDescription,
208 const DifferingParamsContainer &DifferingParams) {
209 auto ChooseOtherName = [](const DifferingParamInfo &ParamInfo) {
210 return ParamInfo.OtherName;
211 };
212 auto ChooseSourceName = [](const DifferingParamInfo &ParamInfo) {
213 return ParamInfo.SourceName;
214 };
215
216 auto ParamDiag =
217 Check->diag(Loc: Location,
218 Description: "differing parameters are named here: (%0), in %1: (%2)",
219 Level: DiagnosticIDs::Level::Note)
220 << joinParameterNames(DifferingParams, ChooseParamName: ChooseOtherName)
221 << OtherDeclarationDescription
222 << joinParameterNames(DifferingParams, ChooseParamName: ChooseSourceName);
223
224 for (const DifferingParamInfo &ParamInfo : DifferingParams) {
225 if (ParamInfo.GenerateFixItHint) {
226 ParamDiag << FixItHint::CreateReplacement(
227 RemoveRange: CharSourceRange::getTokenRange(R: ParamInfo.OtherNameRange),
228 Code: ParamInfo.SourceName);
229 }
230 }
231}
232
233void formatDiagnosticsForDeclarations(
234 InconsistentDeclarationParameterNameCheck *Check,
235 const FunctionDecl *ParameterSourceDeclaration,
236 const FunctionDecl *OriginalDeclaration,
237 const InconsistentDeclarationsContainer &InconsistentDeclarations) {
238 Check->diag(
239 OriginalDeclaration->getLocation(),
240 "function %q0 has %1 other declaration%s1 with different parameter names")
241 << OriginalDeclaration
242 << static_cast<int>(InconsistentDeclarations.size());
243 int Count = 1;
244 for (const InconsistentDeclarationInfo &InconsistentDeclaration :
245 InconsistentDeclarations) {
246 Check->diag(Loc: InconsistentDeclaration.DeclarationLocation,
247 Description: "the %ordinal0 inconsistent declaration seen here",
248 Level: DiagnosticIDs::Level::Note)
249 << Count;
250
251 formatDifferingParamsDiagnostic(
252 Check, Location: InconsistentDeclaration.DeclarationLocation,
253 OtherDeclarationDescription: "the other declaration", DifferingParams: InconsistentDeclaration.DifferingParams);
254
255 ++Count;
256 }
257}
258
259void formatDiagnostics(
260 InconsistentDeclarationParameterNameCheck *Check,
261 const FunctionDecl *ParameterSourceDeclaration,
262 const FunctionDecl *OriginalDeclaration,
263 const InconsistentDeclarationsContainer &InconsistentDeclarations,
264 StringRef FunctionDescription, StringRef ParameterSourceDescription) {
265 for (const InconsistentDeclarationInfo &InconsistentDeclaration :
266 InconsistentDeclarations) {
267 Check->diag(Loc: InconsistentDeclaration.DeclarationLocation,
268 Description: "%0 %q1 has a %2 with different parameter names")
269 << FunctionDescription << OriginalDeclaration
270 << ParameterSourceDescription;
271
272 Check->diag(ParameterSourceDeclaration->getLocation(), "the %0 seen here",
273 DiagnosticIDs::Level::Note)
274 << ParameterSourceDescription;
275
276 formatDifferingParamsDiagnostic(
277 Check, Location: InconsistentDeclaration.DeclarationLocation,
278 OtherDeclarationDescription: ParameterSourceDescription, DifferingParams: InconsistentDeclaration.DifferingParams);
279 }
280}
281
282} // anonymous namespace
283
284void InconsistentDeclarationParameterNameCheck::storeOptions(
285 ClangTidyOptions::OptionMap &Opts) {
286 Options.store(Options&: Opts, LocalName: "IgnoreMacros", Value: IgnoreMacros);
287 Options.store(Options&: Opts, LocalName: "Strict", Value: Strict);
288}
289
290void InconsistentDeclarationParameterNameCheck::registerMatchers(
291 MatchFinder *Finder) {
292 Finder->addMatcher(NodeMatch: functionDecl(hasOtherDeclarations()).bind(ID: "functionDecl"),
293 Action: this);
294}
295
296void InconsistentDeclarationParameterNameCheck::check(
297 const MatchFinder::MatchResult &Result) {
298 const auto *OriginalDeclaration =
299 Result.Nodes.getNodeAs<FunctionDecl>(ID: "functionDecl");
300
301 if (VisitedDeclarations.contains(V: OriginalDeclaration))
302 return; // Avoid multiple warnings.
303
304 const FunctionDecl *ParameterSourceDeclaration =
305 getParameterSourceDeclaration(OriginalDeclaration);
306
307 InconsistentDeclarationsContainer InconsistentDeclarations =
308 findInconsistentDeclarations(OriginalDeclaration,
309 ParameterSourceDeclaration,
310 SM&: *Result.SourceManager, Strict);
311 if (InconsistentDeclarations.empty()) {
312 // Avoid unnecessary further visits.
313 markRedeclarationsAsVisited(OriginalDeclaration);
314 return;
315 }
316
317 SourceLocation StartLoc = OriginalDeclaration->getBeginLoc();
318 if (StartLoc.isMacroID() && IgnoreMacros) {
319 markRedeclarationsAsVisited(OriginalDeclaration);
320 return;
321 }
322
323 if (OriginalDeclaration->getTemplatedKind() ==
324 FunctionDecl::TK_FunctionTemplateSpecialization) {
325 formatDiagnostics(Check: this, ParameterSourceDeclaration, OriginalDeclaration,
326 InconsistentDeclarations,
327 FunctionDescription: "function template specialization",
328 ParameterSourceDescription: "primary template declaration");
329 } else if (ParameterSourceDeclaration->isThisDeclarationADefinition()) {
330 formatDiagnostics(Check: this, ParameterSourceDeclaration, OriginalDeclaration,
331 InconsistentDeclarations, FunctionDescription: "function", ParameterSourceDescription: "definition");
332 } else {
333 formatDiagnosticsForDeclarations(Check: this, ParameterSourceDeclaration,
334 OriginalDeclaration,
335 InconsistentDeclarations);
336 }
337
338 markRedeclarationsAsVisited(OriginalDeclaration);
339}
340
341void InconsistentDeclarationParameterNameCheck::markRedeclarationsAsVisited(
342 const FunctionDecl *OriginalDeclaration) {
343 VisitedDeclarations.insert_range(OriginalDeclaration->redecls());
344}
345
346} // namespace clang::tidy::readability
347

Provided by KDAB

Privacy Policy
Update your C++ knowledge – Modern C++11/14/17 Training
Find out more

source code of clang-tools-extra/clang-tidy/readability/InconsistentDeclarationParameterNameCheck.cpp