llvm-project/clang-tools-extra/clang-tidy/readability/InconsistentDeclarationParameterNameCheck.cpp
Chandler Carruth 2946cd7010 Update the file headers across all of the LLVM projects in the monorepo
to reflect the new license.

We understand that people may be surprised that we're moving the header
entirely to discuss the new license. We checked this carefully with the
Foundation's lawyer and we believe this is the correct approach.

Essentially, all code in the project is now made available by the LLVM
project under our new license, so you will see that the license headers
include that license only. Some of our contributors have contributed
code under our old license, and accordingly, we have retained a copy of
our old license notice in the top-level files in each project and
repository.

llvm-svn: 351636
2019-01-19 08:50:56 +00:00

360 lines
13 KiB
C++

//===--- InconsistentDeclarationParameterNameCheck.cpp - clang-tidy-------===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//
#include "InconsistentDeclarationParameterNameCheck.h"
#include "clang/AST/ASTContext.h"
#include "clang/ASTMatchers/ASTMatchFinder.h"
#include <algorithm>
#include <functional>
#include <sstream>
using namespace clang::ast_matchers;
namespace clang {
namespace tidy {
namespace readability {
namespace {
AST_MATCHER(FunctionDecl, hasOtherDeclarations) {
auto It = Node.redecls_begin();
auto EndIt = Node.redecls_end();
if (It == EndIt)
return false;
++It;
return It != EndIt;
}
struct DifferingParamInfo {
DifferingParamInfo(StringRef SourceName, StringRef OtherName,
SourceRange OtherNameRange, bool GenerateFixItHint)
: SourceName(SourceName), OtherName(OtherName),
OtherNameRange(OtherNameRange), GenerateFixItHint(GenerateFixItHint) {}
StringRef SourceName;
StringRef OtherName;
SourceRange OtherNameRange;
bool GenerateFixItHint;
};
using DifferingParamsContainer = llvm::SmallVector<DifferingParamInfo, 10>;
struct InconsistentDeclarationInfo {
InconsistentDeclarationInfo(SourceLocation DeclarationLocation,
DifferingParamsContainer &&DifferingParams)
: DeclarationLocation(DeclarationLocation),
DifferingParams(std::move(DifferingParams)) {}
SourceLocation DeclarationLocation;
DifferingParamsContainer DifferingParams;
};
using InconsistentDeclarationsContainer =
llvm::SmallVector<InconsistentDeclarationInfo, 2>;
bool checkIfFixItHintIsApplicable(
const FunctionDecl *ParameterSourceDeclaration,
const ParmVarDecl *SourceParam, const FunctionDecl *OriginalDeclaration) {
// Assumptions with regard to function declarations/definition:
// * If both function declaration and definition are seen, assume that
// definition is most up-to-date, and use it to generate replacements.
// * If only function declarations are seen, there is no easy way to tell
// which is up-to-date and which is not, so don't do anything.
// TODO: This may be changed later, but for now it seems the reasonable
// solution.
if (!ParameterSourceDeclaration->isThisDeclarationADefinition())
return false;
// Assumption: if parameter is not referenced in function defintion body, it
// may indicate that it's outdated, so don't touch it.
if (!SourceParam->isReferenced())
return false;
// In case there is the primary template definition and (possibly several)
// template specializations (and each with possibly several redeclarations),
// it is not at all clear what to change.
if (OriginalDeclaration->getTemplatedKind() ==
FunctionDecl::TK_FunctionTemplateSpecialization)
return false;
// Other cases seem OK to allow replacements.
return true;
}
bool nameMatch(StringRef L, StringRef R, bool Strict) {
if (Strict)
return L.empty() || R.empty() || L == R;
// We allow two names if one is a prefix/suffix of the other, ignoring case.
// Important special case: this is true if either parameter has no name!
return L.startswith_lower(R) || R.startswith_lower(L) ||
L.endswith_lower(R) || R.endswith_lower(L);
}
DifferingParamsContainer
findDifferingParamsInDeclaration(const FunctionDecl *ParameterSourceDeclaration,
const FunctionDecl *OtherDeclaration,
const FunctionDecl *OriginalDeclaration,
bool Strict) {
DifferingParamsContainer DifferingParams;
auto SourceParamIt = ParameterSourceDeclaration->param_begin();
auto OtherParamIt = OtherDeclaration->param_begin();
while (SourceParamIt != ParameterSourceDeclaration->param_end() &&
OtherParamIt != OtherDeclaration->param_end()) {
auto SourceParamName = (*SourceParamIt)->getName();
auto OtherParamName = (*OtherParamIt)->getName();
// FIXME: Provide a way to extract commented out parameter name from comment
// next to it.
if (!nameMatch(SourceParamName, OtherParamName, Strict)) {
SourceRange OtherParamNameRange =
DeclarationNameInfo((*OtherParamIt)->getDeclName(),
(*OtherParamIt)->getLocation())
.getSourceRange();
bool GenerateFixItHint = checkIfFixItHintIsApplicable(
ParameterSourceDeclaration, *SourceParamIt, OriginalDeclaration);
DifferingParams.emplace_back(SourceParamName, OtherParamName,
OtherParamNameRange, GenerateFixItHint);
}
++SourceParamIt;
++OtherParamIt;
}
return DifferingParams;
}
InconsistentDeclarationsContainer
findInconsistentDeclarations(const FunctionDecl *OriginalDeclaration,
const FunctionDecl *ParameterSourceDeclaration,
SourceManager &SM, bool Strict) {
InconsistentDeclarationsContainer InconsistentDeclarations;
SourceLocation ParameterSourceLocation =
ParameterSourceDeclaration->getLocation();
for (const FunctionDecl *OtherDeclaration : OriginalDeclaration->redecls()) {
SourceLocation OtherLocation = OtherDeclaration->getLocation();
if (OtherLocation != ParameterSourceLocation) { // Skip self.
DifferingParamsContainer DifferingParams =
findDifferingParamsInDeclaration(ParameterSourceDeclaration,
OtherDeclaration,
OriginalDeclaration, Strict);
if (!DifferingParams.empty()) {
InconsistentDeclarations.emplace_back(OtherDeclaration->getLocation(),
std::move(DifferingParams));
}
}
}
// Sort in order of appearance in translation unit to generate clear
// diagnostics.
std::sort(InconsistentDeclarations.begin(), InconsistentDeclarations.end(),
[&SM](const InconsistentDeclarationInfo &Info1,
const InconsistentDeclarationInfo &Info2) {
return SM.isBeforeInTranslationUnit(Info1.DeclarationLocation,
Info2.DeclarationLocation);
});
return InconsistentDeclarations;
}
const FunctionDecl *
getParameterSourceDeclaration(const FunctionDecl *OriginalDeclaration) {
const FunctionTemplateDecl *PrimaryTemplate =
OriginalDeclaration->getPrimaryTemplate();
if (PrimaryTemplate != nullptr) {
// In case of template specializations, use primary template declaration as
// the source of parameter names.
return PrimaryTemplate->getTemplatedDecl();
}
// In other cases, try to change to function definition, if available.
if (OriginalDeclaration->isThisDeclarationADefinition())
return OriginalDeclaration;
for (const FunctionDecl *OtherDeclaration : OriginalDeclaration->redecls()) {
if (OtherDeclaration->isThisDeclarationADefinition()) {
return OtherDeclaration;
}
}
// No definition found, so return original declaration.
return OriginalDeclaration;
}
std::string joinParameterNames(
const DifferingParamsContainer &DifferingParams,
llvm::function_ref<StringRef(const DifferingParamInfo &)> ChooseParamName) {
llvm::SmallVector<char, 40> Buffer;
llvm::raw_svector_ostream Str(Buffer);
bool First = true;
for (const DifferingParamInfo &ParamInfo : DifferingParams) {
if (First)
First = false;
else
Str << ", ";
Str << "'" << ChooseParamName(ParamInfo).str() << "'";
}
return Str.str().str();
}
void formatDifferingParamsDiagnostic(
InconsistentDeclarationParameterNameCheck *Check, SourceLocation Location,
StringRef OtherDeclarationDescription,
const DifferingParamsContainer &DifferingParams) {
auto ChooseOtherName = [](const DifferingParamInfo &ParamInfo) {
return ParamInfo.OtherName;
};
auto ChooseSourceName = [](const DifferingParamInfo &ParamInfo) {
return ParamInfo.SourceName;
};
auto ParamDiag =
Check->diag(Location,
"differing parameters are named here: (%0), in %1: (%2)",
DiagnosticIDs::Level::Note)
<< joinParameterNames(DifferingParams, ChooseOtherName)
<< OtherDeclarationDescription
<< joinParameterNames(DifferingParams, ChooseSourceName);
for (const DifferingParamInfo &ParamInfo : DifferingParams) {
if (ParamInfo.GenerateFixItHint) {
ParamDiag << FixItHint::CreateReplacement(
CharSourceRange::getTokenRange(ParamInfo.OtherNameRange),
ParamInfo.SourceName);
}
}
}
void formatDiagnosticsForDeclarations(
InconsistentDeclarationParameterNameCheck *Check,
const FunctionDecl *ParameterSourceDeclaration,
const FunctionDecl *OriginalDeclaration,
const InconsistentDeclarationsContainer &InconsistentDeclarations) {
Check->diag(
OriginalDeclaration->getLocation(),
"function %q0 has %1 other declaration%s1 with different parameter names")
<< OriginalDeclaration
<< static_cast<int>(InconsistentDeclarations.size());
int Count = 1;
for (const InconsistentDeclarationInfo &InconsistentDeclaration :
InconsistentDeclarations) {
Check->diag(InconsistentDeclaration.DeclarationLocation,
"the %ordinal0 inconsistent declaration seen here",
DiagnosticIDs::Level::Note)
<< Count;
formatDifferingParamsDiagnostic(
Check, InconsistentDeclaration.DeclarationLocation,
"the other declaration", InconsistentDeclaration.DifferingParams);
++Count;
}
}
void formatDiagnostics(
InconsistentDeclarationParameterNameCheck *Check,
const FunctionDecl *ParameterSourceDeclaration,
const FunctionDecl *OriginalDeclaration,
const InconsistentDeclarationsContainer &InconsistentDeclarations,
StringRef FunctionDescription, StringRef ParameterSourceDescription) {
for (const InconsistentDeclarationInfo &InconsistentDeclaration :
InconsistentDeclarations) {
Check->diag(InconsistentDeclaration.DeclarationLocation,
"%0 %q1 has a %2 with different parameter names")
<< FunctionDescription << OriginalDeclaration
<< ParameterSourceDescription;
Check->diag(ParameterSourceDeclaration->getLocation(), "the %0 seen here",
DiagnosticIDs::Level::Note)
<< ParameterSourceDescription;
formatDifferingParamsDiagnostic(
Check, InconsistentDeclaration.DeclarationLocation,
ParameterSourceDescription, InconsistentDeclaration.DifferingParams);
}
}
} // anonymous namespace
void InconsistentDeclarationParameterNameCheck::storeOptions(
ClangTidyOptions::OptionMap &Opts) {
Options.store(Opts, "IgnoreMacros", IgnoreMacros);
Options.store(Opts, "Strict", Strict);
}
void InconsistentDeclarationParameterNameCheck::registerMatchers(
MatchFinder *Finder) {
Finder->addMatcher(functionDecl(unless(isImplicit()), hasOtherDeclarations())
.bind("functionDecl"),
this);
}
void InconsistentDeclarationParameterNameCheck::check(
const MatchFinder::MatchResult &Result) {
const auto *OriginalDeclaration =
Result.Nodes.getNodeAs<FunctionDecl>("functionDecl");
if (VisitedDeclarations.count(OriginalDeclaration) > 0)
return; // Avoid multiple warnings.
const FunctionDecl *ParameterSourceDeclaration =
getParameterSourceDeclaration(OriginalDeclaration);
InconsistentDeclarationsContainer InconsistentDeclarations =
findInconsistentDeclarations(OriginalDeclaration,
ParameterSourceDeclaration,
*Result.SourceManager, Strict);
if (InconsistentDeclarations.empty()) {
// Avoid unnecessary further visits.
markRedeclarationsAsVisited(OriginalDeclaration);
return;
}
SourceLocation StartLoc = OriginalDeclaration->getBeginLoc();
if (StartLoc.isMacroID() && IgnoreMacros) {
markRedeclarationsAsVisited(OriginalDeclaration);
return;
}
if (OriginalDeclaration->getTemplatedKind() ==
FunctionDecl::TK_FunctionTemplateSpecialization) {
formatDiagnostics(this, ParameterSourceDeclaration, OriginalDeclaration,
InconsistentDeclarations,
"function template specialization",
"primary template declaration");
} else if (ParameterSourceDeclaration->isThisDeclarationADefinition()) {
formatDiagnostics(this, ParameterSourceDeclaration, OriginalDeclaration,
InconsistentDeclarations, "function", "definition");
} else {
formatDiagnosticsForDeclarations(this, ParameterSourceDeclaration,
OriginalDeclaration,
InconsistentDeclarations);
}
markRedeclarationsAsVisited(OriginalDeclaration);
}
void InconsistentDeclarationParameterNameCheck::markRedeclarationsAsVisited(
const FunctionDecl *OriginalDeclaration) {
for (const FunctionDecl *Redecl : OriginalDeclaration->redecls()) {
VisitedDeclarations.insert(Redecl);
}
}
} // namespace readability
} // namespace tidy
} // namespace clang