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