blob: 64a955f68030897d367efb2b936e055fb61f1db7 [file] [log] [blame]
Alexander Kornienko11d4d642015-09-10 10:07:11 +00001//===--- InconsistentDeclarationParameterNameCheck.cpp - clang-tidy-------===//
2//
Chandler Carruth2946cd72019-01-19 08:50:56 +00003// 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
Alexander Kornienko11d4d642015-09-10 10:07:11 +00006//
7//===----------------------------------------------------------------------===//
8
9#include "InconsistentDeclarationParameterNameCheck.h"
10#include "clang/AST/ASTContext.h"
11#include "clang/ASTMatchers/ASTMatchFinder.h"
12
13#include <algorithm>
14#include <functional>
15#include <sstream>
16
Etienne Bergeron456177b2016-05-02 18:00:29 +000017using namespace clang::ast_matchers;
18
Alexander Kornienko11d4d642015-09-10 10:07:11 +000019namespace clang {
20namespace tidy {
21namespace readability {
22
Alexander Kornienko11d4d642015-09-10 10:07:11 +000023namespace {
24
25AST_MATCHER(FunctionDecl, hasOtherDeclarations) {
26 auto It = Node.redecls_begin();
27 auto EndIt = Node.redecls_end();
28
29 if (It == EndIt)
30 return false;
31
32 ++It;
33 return It != EndIt;
34}
35
36struct DifferingParamInfo {
37 DifferingParamInfo(StringRef SourceName, StringRef OtherName,
38 SourceRange OtherNameRange, bool GenerateFixItHint)
39 : SourceName(SourceName), OtherName(OtherName),
40 OtherNameRange(OtherNameRange), GenerateFixItHint(GenerateFixItHint) {}
41
42 StringRef SourceName;
43 StringRef OtherName;
44 SourceRange OtherNameRange;
45 bool GenerateFixItHint;
46};
47
48using DifferingParamsContainer = llvm::SmallVector<DifferingParamInfo, 10>;
49
50struct InconsistentDeclarationInfo {
51 InconsistentDeclarationInfo(SourceLocation DeclarationLocation,
52 DifferingParamsContainer &&DifferingParams)
53 : DeclarationLocation(DeclarationLocation),
54 DifferingParams(std::move(DifferingParams)) {}
55
56 SourceLocation DeclarationLocation;
57 DifferingParamsContainer DifferingParams;
58};
59
60using InconsistentDeclarationsContainer =
61 llvm::SmallVector<InconsistentDeclarationInfo, 2>;
62
63bool checkIfFixItHintIsApplicable(
64 const FunctionDecl *ParameterSourceDeclaration,
65 const ParmVarDecl *SourceParam, const FunctionDecl *OriginalDeclaration) {
66 // Assumptions with regard to function declarations/definition:
67 // * If both function declaration and definition are seen, assume that
Piotr Dziwinski0a295572015-10-24 20:11:47 +000068 // definition is most up-to-date, and use it to generate replacements.
Alexander Kornienko11d4d642015-09-10 10:07:11 +000069 // * If only function declarations are seen, there is no easy way to tell
Piotr Dziwinski0a295572015-10-24 20:11:47 +000070 // which is up-to-date and which is not, so don't do anything.
Alexander Kornienko11d4d642015-09-10 10:07:11 +000071 // TODO: This may be changed later, but for now it seems the reasonable
72 // solution.
73 if (!ParameterSourceDeclaration->isThisDeclarationADefinition())
74 return false;
75
76 // Assumption: if parameter is not referenced in function defintion body, it
77 // may indicate that it's outdated, so don't touch it.
78 if (!SourceParam->isReferenced())
79 return false;
80
81 // In case there is the primary template definition and (possibly several)
82 // template specializations (and each with possibly several redeclarations),
83 // it is not at all clear what to change.
84 if (OriginalDeclaration->getTemplatedKind() ==
85 FunctionDecl::TK_FunctionTemplateSpecialization)
86 return false;
87
88 // Other cases seem OK to allow replacements.
89 return true;
90}
91
Sam McCall57b66c82018-07-13 11:41:56 +000092bool nameMatch(StringRef L, StringRef R, bool Strict) {
93 if (Strict)
94 return L.empty() || R.empty() || L == R;
95 // We allow two names if one is a prefix/suffix of the other, ignoring case.
96 // Important special case: this is true if either parameter has no name!
97 return L.startswith_lower(R) || R.startswith_lower(L) ||
98 L.endswith_lower(R) || R.endswith_lower(L);
99}
100
Alexander Kornienko11d4d642015-09-10 10:07:11 +0000101DifferingParamsContainer
102findDifferingParamsInDeclaration(const FunctionDecl *ParameterSourceDeclaration,
103 const FunctionDecl *OtherDeclaration,
Sam McCall57b66c82018-07-13 11:41:56 +0000104 const FunctionDecl *OriginalDeclaration,
105 bool Strict) {
Alexander Kornienko11d4d642015-09-10 10:07:11 +0000106 DifferingParamsContainer DifferingParams;
107
108 auto SourceParamIt = ParameterSourceDeclaration->param_begin();
109 auto OtherParamIt = OtherDeclaration->param_begin();
110
111 while (SourceParamIt != ParameterSourceDeclaration->param_end() &&
112 OtherParamIt != OtherDeclaration->param_end()) {
113 auto SourceParamName = (*SourceParamIt)->getName();
114 auto OtherParamName = (*OtherParamIt)->getName();
115
116 // FIXME: Provide a way to extract commented out parameter name from comment
117 // next to it.
Sam McCall57b66c82018-07-13 11:41:56 +0000118 if (!nameMatch(SourceParamName, OtherParamName, Strict)) {
Alexander Kornienko11d4d642015-09-10 10:07:11 +0000119 SourceRange OtherParamNameRange =
120 DeclarationNameInfo((*OtherParamIt)->getDeclName(),
Mandeep Singh Grang7c7ea7d2016-11-08 07:50:19 +0000121 (*OtherParamIt)->getLocation())
122 .getSourceRange();
Alexander Kornienko11d4d642015-09-10 10:07:11 +0000123
124 bool GenerateFixItHint = checkIfFixItHintIsApplicable(
125 ParameterSourceDeclaration, *SourceParamIt, OriginalDeclaration);
126
127 DifferingParams.emplace_back(SourceParamName, OtherParamName,
128 OtherParamNameRange, GenerateFixItHint);
129 }
130
131 ++SourceParamIt;
132 ++OtherParamIt;
133 }
134
135 return DifferingParams;
136}
137
138InconsistentDeclarationsContainer
Sam McCall57b66c82018-07-13 11:41:56 +0000139findInconsistentDeclarations(const FunctionDecl *OriginalDeclaration,
Alexander Kornienko11d4d642015-09-10 10:07:11 +0000140 const FunctionDecl *ParameterSourceDeclaration,
Sam McCall57b66c82018-07-13 11:41:56 +0000141 SourceManager &SM, bool Strict) {
Alexander Kornienko11d4d642015-09-10 10:07:11 +0000142 InconsistentDeclarationsContainer InconsistentDeclarations;
143 SourceLocation ParameterSourceLocation =
144 ParameterSourceDeclaration->getLocation();
145
146 for (const FunctionDecl *OtherDeclaration : OriginalDeclaration->redecls()) {
147 SourceLocation OtherLocation = OtherDeclaration->getLocation();
148 if (OtherLocation != ParameterSourceLocation) { // Skip self.
149 DifferingParamsContainer DifferingParams =
150 findDifferingParamsInDeclaration(ParameterSourceDeclaration,
151 OtherDeclaration,
Sam McCall57b66c82018-07-13 11:41:56 +0000152 OriginalDeclaration, Strict);
Alexander Kornienko11d4d642015-09-10 10:07:11 +0000153 if (!DifferingParams.empty()) {
154 InconsistentDeclarations.emplace_back(OtherDeclaration->getLocation(),
155 std::move(DifferingParams));
156 }
157 }
158 }
159
160 // Sort in order of appearance in translation unit to generate clear
161 // diagnostics.
162 std::sort(InconsistentDeclarations.begin(), InconsistentDeclarations.end(),
163 [&SM](const InconsistentDeclarationInfo &Info1,
164 const InconsistentDeclarationInfo &Info2) {
165 return SM.isBeforeInTranslationUnit(Info1.DeclarationLocation,
166 Info2.DeclarationLocation);
167 });
168 return InconsistentDeclarations;
169}
170
171const FunctionDecl *
172getParameterSourceDeclaration(const FunctionDecl *OriginalDeclaration) {
173 const FunctionTemplateDecl *PrimaryTemplate =
174 OriginalDeclaration->getPrimaryTemplate();
175 if (PrimaryTemplate != nullptr) {
176 // In case of template specializations, use primary template declaration as
177 // the source of parameter names.
178 return PrimaryTemplate->getTemplatedDecl();
179 }
180
181 // In other cases, try to change to function definition, if available.
182
183 if (OriginalDeclaration->isThisDeclarationADefinition())
184 return OriginalDeclaration;
185
186 for (const FunctionDecl *OtherDeclaration : OriginalDeclaration->redecls()) {
187 if (OtherDeclaration->isThisDeclarationADefinition()) {
188 return OtherDeclaration;
189 }
190 }
191
192 // No definition found, so return original declaration.
193 return OriginalDeclaration;
194}
195
196std::string joinParameterNames(
197 const DifferingParamsContainer &DifferingParams,
Benjamin Kramer51a9cc92016-06-15 15:46:10 +0000198 llvm::function_ref<StringRef(const DifferingParamInfo &)> ChooseParamName) {
Alexander Kornienko11d4d642015-09-10 10:07:11 +0000199 llvm::SmallVector<char, 40> Buffer;
200 llvm::raw_svector_ostream Str(Buffer);
201 bool First = true;
202 for (const DifferingParamInfo &ParamInfo : DifferingParams) {
203 if (First)
204 First = false;
205 else
206 Str << ", ";
207
208 Str << "'" << ChooseParamName(ParamInfo).str() << "'";
209 }
210 return Str.str().str();
211}
212
213void formatDifferingParamsDiagnostic(
Mandeep Singh Grang7c7ea7d2016-11-08 07:50:19 +0000214 InconsistentDeclarationParameterNameCheck *Check, SourceLocation Location,
215 StringRef OtherDeclarationDescription,
Alexander Kornienko11d4d642015-09-10 10:07:11 +0000216 const DifferingParamsContainer &DifferingParams) {
Mandeep Singh Grang7c7ea7d2016-11-08 07:50:19 +0000217 auto ChooseOtherName = [](const DifferingParamInfo &ParamInfo) {
218 return ParamInfo.OtherName;
219 };
220 auto ChooseSourceName = [](const DifferingParamInfo &ParamInfo) {
221 return ParamInfo.SourceName;
222 };
Alexander Kornienko11d4d642015-09-10 10:07:11 +0000223
224 auto ParamDiag =
225 Check->diag(Location,
226 "differing parameters are named here: (%0), in %1: (%2)",
227 DiagnosticIDs::Level::Note)
228 << joinParameterNames(DifferingParams, ChooseOtherName)
229 << OtherDeclarationDescription
230 << joinParameterNames(DifferingParams, ChooseSourceName);
231
232 for (const DifferingParamInfo &ParamInfo : DifferingParams) {
233 if (ParamInfo.GenerateFixItHint) {
234 ParamDiag << FixItHint::CreateReplacement(
235 CharSourceRange::getTokenRange(ParamInfo.OtherNameRange),
236 ParamInfo.SourceName);
237 }
238 }
239}
240
241void formatDiagnosticsForDeclarations(
242 InconsistentDeclarationParameterNameCheck *Check,
243 const FunctionDecl *ParameterSourceDeclaration,
244 const FunctionDecl *OriginalDeclaration,
245 const InconsistentDeclarationsContainer &InconsistentDeclarations) {
246 Check->diag(
247 OriginalDeclaration->getLocation(),
248 "function %q0 has %1 other declaration%s1 with different parameter names")
249 << OriginalDeclaration
250 << static_cast<int>(InconsistentDeclarations.size());
251 int Count = 1;
252 for (const InconsistentDeclarationInfo &InconsistentDeclaration :
253 InconsistentDeclarations) {
254 Check->diag(InconsistentDeclaration.DeclarationLocation,
255 "the %ordinal0 inconsistent declaration seen here",
256 DiagnosticIDs::Level::Note)
257 << Count;
258
259 formatDifferingParamsDiagnostic(
260 Check, InconsistentDeclaration.DeclarationLocation,
261 "the other declaration", InconsistentDeclaration.DifferingParams);
262
263 ++Count;
264 }
265}
266
267void formatDiagnostics(
268 InconsistentDeclarationParameterNameCheck *Check,
269 const FunctionDecl *ParameterSourceDeclaration,
270 const FunctionDecl *OriginalDeclaration,
271 const InconsistentDeclarationsContainer &InconsistentDeclarations,
272 StringRef FunctionDescription, StringRef ParameterSourceDescription) {
273 for (const InconsistentDeclarationInfo &InconsistentDeclaration :
274 InconsistentDeclarations) {
275 Check->diag(InconsistentDeclaration.DeclarationLocation,
276 "%0 %q1 has a %2 with different parameter names")
277 << FunctionDescription << OriginalDeclaration
278 << ParameterSourceDescription;
279
280 Check->diag(ParameterSourceDeclaration->getLocation(), "the %0 seen here",
281 DiagnosticIDs::Level::Note)
282 << ParameterSourceDescription;
283
284 formatDifferingParamsDiagnostic(
285 Check, InconsistentDeclaration.DeclarationLocation,
286 ParameterSourceDescription, InconsistentDeclaration.DifferingParams);
287 }
288}
289
290} // anonymous namespace
291
Miklos Vajna063e6cc2018-01-05 23:22:10 +0000292void InconsistentDeclarationParameterNameCheck::storeOptions(
293 ClangTidyOptions::OptionMap &Opts) {
294 Options.store(Opts, "IgnoreMacros", IgnoreMacros);
Sam McCall57b66c82018-07-13 11:41:56 +0000295 Options.store(Opts, "Strict", Strict);
Miklos Vajna063e6cc2018-01-05 23:22:10 +0000296}
297
Alexander Kornienko11d4d642015-09-10 10:07:11 +0000298void InconsistentDeclarationParameterNameCheck::registerMatchers(
299 MatchFinder *Finder) {
300 Finder->addMatcher(functionDecl(unless(isImplicit()), hasOtherDeclarations())
301 .bind("functionDecl"),
302 this);
303}
304
305void InconsistentDeclarationParameterNameCheck::check(
306 const MatchFinder::MatchResult &Result) {
307 const auto *OriginalDeclaration =
308 Result.Nodes.getNodeAs<FunctionDecl>("functionDecl");
309
310 if (VisitedDeclarations.count(OriginalDeclaration) > 0)
311 return; // Avoid multiple warnings.
312
313 const FunctionDecl *ParameterSourceDeclaration =
314 getParameterSourceDeclaration(OriginalDeclaration);
315
316 InconsistentDeclarationsContainer InconsistentDeclarations =
Sam McCall57b66c82018-07-13 11:41:56 +0000317 findInconsistentDeclarations(OriginalDeclaration,
318 ParameterSourceDeclaration,
319 *Result.SourceManager, Strict);
Alexander Kornienko11d4d642015-09-10 10:07:11 +0000320 if (InconsistentDeclarations.empty()) {
321 // Avoid unnecessary further visits.
322 markRedeclarationsAsVisited(OriginalDeclaration);
323 return;
324 }
325
Stephen Kelly43465bf2018-08-09 22:42:26 +0000326 SourceLocation StartLoc = OriginalDeclaration->getBeginLoc();
Miklos Vajna063e6cc2018-01-05 23:22:10 +0000327 if (StartLoc.isMacroID() && IgnoreMacros) {
328 markRedeclarationsAsVisited(OriginalDeclaration);
329 return;
330 }
331
Alexander Kornienko11d4d642015-09-10 10:07:11 +0000332 if (OriginalDeclaration->getTemplatedKind() ==
333 FunctionDecl::TK_FunctionTemplateSpecialization) {
334 formatDiagnostics(this, ParameterSourceDeclaration, OriginalDeclaration,
335 InconsistentDeclarations,
336 "function template specialization",
337 "primary template declaration");
338 } else if (ParameterSourceDeclaration->isThisDeclarationADefinition()) {
339 formatDiagnostics(this, ParameterSourceDeclaration, OriginalDeclaration,
340 InconsistentDeclarations, "function", "definition");
341 } else {
342 formatDiagnosticsForDeclarations(this, ParameterSourceDeclaration,
343 OriginalDeclaration,
344 InconsistentDeclarations);
345 }
346
347 markRedeclarationsAsVisited(OriginalDeclaration);
348}
349
350void InconsistentDeclarationParameterNameCheck::markRedeclarationsAsVisited(
351 const FunctionDecl *OriginalDeclaration) {
352 for (const FunctionDecl *Redecl : OriginalDeclaration->redecls()) {
353 VisitedDeclarations.insert(Redecl);
354 }
355}
356
357} // namespace readability
358} // namespace tidy
359} // namespace clang