Factor out renaming logic from readability-identifier-naming

Before this patch, readability-identifier-naming contained a significant amount
of logic for (a) checking the style of identifiers, followed by (b) renaming/
applying fix-its. This patch factors out (b) into a separate base class so that
it can be reused by other checks that want to do renaming. This also cleans up
readability-identifier-naming significantly, since now it only needs to be
concerned with the interesting details of (a).
This commit is contained in:
Logan Smith 2020-01-16 16:29:19 -05:00 committed by Aaron Ballman
parent c299d1981d
commit d5c6b8407c
5 changed files with 625 additions and 425 deletions

View File

@ -22,44 +22,6 @@
using namespace clang::ast_matchers;
namespace llvm {
/// Specialisation of DenseMapInfo to allow NamingCheckId objects in DenseMaps
template <>
struct DenseMapInfo<
clang::tidy::readability::IdentifierNamingCheck::NamingCheckId> {
using NamingCheckId =
clang::tidy::readability::IdentifierNamingCheck::NamingCheckId;
static inline NamingCheckId getEmptyKey() {
return NamingCheckId(
clang::SourceLocation::getFromRawEncoding(static_cast<unsigned>(-1)),
"EMPTY");
}
static inline NamingCheckId getTombstoneKey() {
return NamingCheckId(
clang::SourceLocation::getFromRawEncoding(static_cast<unsigned>(-2)),
"TOMBSTONE");
}
static unsigned getHashValue(NamingCheckId Val) {
assert(Val != getEmptyKey() && "Cannot hash the empty key!");
assert(Val != getTombstoneKey() && "Cannot hash the tombstone key!");
std::hash<NamingCheckId::second_type> SecondHash;
return Val.first.getRawEncoding() + SecondHash(Val.second);
}
static bool isEqual(const NamingCheckId &LHS, const NamingCheckId &RHS) {
if (RHS == getEmptyKey())
return LHS == getEmptyKey();
if (RHS == getTombstoneKey())
return LHS == getTombstoneKey();
return LHS == RHS;
}
};
} // namespace llvm
namespace clang {
namespace tidy {
namespace readability {
@ -164,7 +126,7 @@ private:
IdentifierNamingCheck::IdentifierNamingCheck(StringRef Name,
ClangTidyContext *Context)
: ClangTidyCheck(Name, Context) {
: RenamerClangTidyCheck(Name, Context) {
auto const fromString = [](StringRef Str) {
return llvm::StringSwitch<llvm::Optional<CaseType>>(Str)
.Case("aNy_CasE", CT_AnyCase)
@ -233,39 +195,6 @@ void IdentifierNamingCheck::storeOptions(ClangTidyOptions::OptionMap &Opts) {
Options.store(Opts, "IgnoreFailedSplit", IgnoreFailedSplit);
}
void IdentifierNamingCheck::registerMatchers(MatchFinder *Finder) {
Finder->addMatcher(namedDecl().bind("decl"), this);
Finder->addMatcher(usingDecl().bind("using"), this);
Finder->addMatcher(declRefExpr().bind("declRef"), this);
Finder->addMatcher(cxxConstructorDecl(unless(isImplicit())).bind("classRef"),
this);
Finder->addMatcher(cxxDestructorDecl(unless(isImplicit())).bind("classRef"),
this);
Finder->addMatcher(typeLoc().bind("typeLoc"), this);
Finder->addMatcher(nestedNameSpecifierLoc().bind("nestedNameLoc"), this);
Finder->addMatcher(
functionDecl(unless(cxxMethodDecl(isImplicit())),
hasBody(forEachDescendant(memberExpr().bind("memberExpr")))),
this);
Finder->addMatcher(
cxxConstructorDecl(
unless(isImplicit()),
forEachConstructorInitializer(
allOf(isWritten(), withInitializer(forEachDescendant(
memberExpr().bind("memberExpr")))))),
this);
Finder->addMatcher(fieldDecl(hasInClassInitializer(
forEachDescendant(memberExpr().bind("memberExpr")))),
this);
}
void IdentifierNamingCheck::registerPPCallbacks(
const SourceManager &SM, Preprocessor *PP, Preprocessor *ModuleExpanderPP) {
ModuleExpanderPP->addPPCallbacks(
std::make_unique<IdentifierNamingCheckPPCallbacks>(ModuleExpanderPP,
this));
}
static bool matchesStyle(StringRef Name,
IdentifierNamingCheck::NamingStyle Style) {
static llvm::Regex Matchers[] = {
@ -683,243 +612,47 @@ static StyleKind findStyleKind(
return SK_Invalid;
}
static void addUsage(IdentifierNamingCheck::NamingCheckFailureMap &Failures,
const IdentifierNamingCheck::NamingCheckId &Decl,
SourceRange Range, SourceManager *SourceMgr = nullptr) {
// Do nothing if the provided range is invalid.
if (Range.getBegin().isInvalid() || Range.getEnd().isInvalid())
return;
llvm::Optional<RenamerClangTidyCheck::FailureInfo>
IdentifierNamingCheck::GetDeclFailureInfo(const NamedDecl *Decl,
const SourceManager &SM) const {
StyleKind SK = findStyleKind(Decl, NamingStyles);
if (SK == SK_Invalid)
return None;
// If we have a source manager, use it to convert to the spelling location for
// performing the fix. This is necessary because macros can map the same
// spelling location to different source locations, and we only want to fix
// the token once, before it is expanded by the macro.
SourceLocation FixLocation = Range.getBegin();
if (SourceMgr)
FixLocation = SourceMgr->getSpellingLoc(FixLocation);
if (FixLocation.isInvalid())
return;
if (!NamingStyles[SK])
return None;
// Try to insert the identifier location in the Usages map, and bail out if it
// is already in there
auto &Failure = Failures[Decl];
if (!Failure.RawUsageLocs.insert(FixLocation.getRawEncoding()).second)
return;
const NamingStyle &Style = *NamingStyles[SK];
StringRef Name = Decl->getName();
if (matchesStyle(Name, Style))
return None;
if (!Failure.ShouldFix())
return;
std::string KindName = fixupWithCase(StyleNames[SK], CT_LowerCase);
std::replace(KindName.begin(), KindName.end(), '_', ' ');
if (!utils::rangeCanBeFixed(Range, SourceMgr))
Failure.FixStatus = IdentifierNamingCheck::ShouldFixStatus::InsideMacro;
std::string Fixup = fixupWithStyle(Name, Style);
if (StringRef(Fixup).equals(Name)) {
if (!IgnoreFailedSplit) {
LLVM_DEBUG(llvm::dbgs()
<< Decl->getBeginLoc().printToString(SM)
<< llvm::format(": unable to split words for %s '%s'\n",
KindName.c_str(), Name.str().c_str()));
}
return None;
}
return FailureInfo{std::move(KindName), std::move(Fixup)};
}
/// Convenience method when the usage to be added is a NamedDecl
static void addUsage(IdentifierNamingCheck::NamingCheckFailureMap &Failures,
const NamedDecl *Decl, SourceRange Range,
SourceManager *SourceMgr = nullptr) {
return addUsage(Failures,
IdentifierNamingCheck::NamingCheckId(Decl->getLocation(),
Decl->getNameAsString()),
Range, SourceMgr);
}
void IdentifierNamingCheck::check(const MatchFinder::MatchResult &Result) {
if (const auto *Decl =
Result.Nodes.getNodeAs<CXXConstructorDecl>("classRef")) {
addUsage(NamingCheckFailures, Decl->getParent(),
Decl->getNameInfo().getSourceRange());
for (const auto *Init : Decl->inits()) {
if (!Init->isWritten() || Init->isInClassMemberInitializer())
continue;
if (const auto *FD = Init->getAnyMember())
addUsage(NamingCheckFailures, FD,
SourceRange(Init->getMemberLocation()));
// Note: delegating constructors and base class initializers are handled
// via the "typeLoc" matcher.
}
return;
}
if (const auto *Decl =
Result.Nodes.getNodeAs<CXXDestructorDecl>("classRef")) {
SourceRange Range = Decl->getNameInfo().getSourceRange();
if (Range.getBegin().isInvalid())
return;
// The first token that will be found is the ~ (or the equivalent trigraph),
// we want instead to replace the next token, that will be the identifier.
Range.setBegin(CharSourceRange::getTokenRange(Range).getEnd());
addUsage(NamingCheckFailures, Decl->getParent(), Range);
return;
}
if (const auto *Loc = Result.Nodes.getNodeAs<TypeLoc>("typeLoc")) {
NamedDecl *Decl = nullptr;
if (const auto &Ref = Loc->getAs<TagTypeLoc>()) {
Decl = Ref.getDecl();
} else if (const auto &Ref = Loc->getAs<InjectedClassNameTypeLoc>()) {
Decl = Ref.getDecl();
} else if (const auto &Ref = Loc->getAs<UnresolvedUsingTypeLoc>()) {
Decl = Ref.getDecl();
} else if (const auto &Ref = Loc->getAs<TemplateTypeParmTypeLoc>()) {
Decl = Ref.getDecl();
}
if (Decl) {
addUsage(NamingCheckFailures, Decl, Loc->getSourceRange());
return;
}
if (const auto &Ref = Loc->getAs<TemplateSpecializationTypeLoc>()) {
const auto *Decl =
Ref.getTypePtr()->getTemplateName().getAsTemplateDecl();
SourceRange Range(Ref.getTemplateNameLoc(), Ref.getTemplateNameLoc());
if (const auto *ClassDecl = dyn_cast<TemplateDecl>(Decl)) {
if (const auto *TemplDecl = ClassDecl->getTemplatedDecl())
addUsage(NamingCheckFailures, TemplDecl, Range);
return;
}
}
if (const auto &Ref =
Loc->getAs<DependentTemplateSpecializationTypeLoc>()) {
if (const auto *Decl = Ref.getTypePtr()->getAsTagDecl())
addUsage(NamingCheckFailures, Decl, Loc->getSourceRange());
return;
}
}
if (const auto *Loc =
Result.Nodes.getNodeAs<NestedNameSpecifierLoc>("nestedNameLoc")) {
if (NestedNameSpecifier *Spec = Loc->getNestedNameSpecifier()) {
if (NamespaceDecl *Decl = Spec->getAsNamespace()) {
addUsage(NamingCheckFailures, Decl, Loc->getLocalSourceRange());
return;
}
}
}
if (const auto *Decl = Result.Nodes.getNodeAs<UsingDecl>("using")) {
for (const auto *Shadow : Decl->shadows()) {
addUsage(NamingCheckFailures, Shadow->getTargetDecl(),
Decl->getNameInfo().getSourceRange());
}
return;
}
if (const auto *DeclRef = Result.Nodes.getNodeAs<DeclRefExpr>("declRef")) {
SourceRange Range = DeclRef->getNameInfo().getSourceRange();
addUsage(NamingCheckFailures, DeclRef->getDecl(), Range,
Result.SourceManager);
return;
}
if (const auto *MemberRef =
Result.Nodes.getNodeAs<MemberExpr>("memberExpr")) {
SourceRange Range = MemberRef->getMemberNameInfo().getSourceRange();
addUsage(NamingCheckFailures, MemberRef->getMemberDecl(), Range,
Result.SourceManager);
return;
}
if (const auto *Decl = Result.Nodes.getNodeAs<NamedDecl>("decl")) {
if (!Decl->getIdentifier() || Decl->getName().empty() || Decl->isImplicit())
return;
// Fix type aliases in value declarations
if (const auto *Value = Result.Nodes.getNodeAs<ValueDecl>("decl")) {
if (const auto *TypePtr = Value->getType().getTypePtrOrNull()) {
if (const auto *Typedef = TypePtr->getAs<TypedefType>()) {
addUsage(NamingCheckFailures, Typedef->getDecl(),
Value->getSourceRange());
}
}
}
// Fix type aliases in function declarations
if (const auto *Value = Result.Nodes.getNodeAs<FunctionDecl>("decl")) {
if (const auto *Typedef =
Value->getReturnType().getTypePtr()->getAs<TypedefType>()) {
addUsage(NamingCheckFailures, Typedef->getDecl(),
Value->getSourceRange());
}
for (unsigned i = 0; i < Value->getNumParams(); ++i) {
if (const auto *Typedef = Value->parameters()[i]
->getType()
.getTypePtr()
->getAs<TypedefType>()) {
addUsage(NamingCheckFailures, Typedef->getDecl(),
Value->getSourceRange());
}
}
}
// Ignore ClassTemplateSpecializationDecl which are creating duplicate
// replacements with CXXRecordDecl
if (isa<ClassTemplateSpecializationDecl>(Decl))
return;
StyleKind SK = findStyleKind(Decl, NamingStyles);
if (SK == SK_Invalid)
return;
if (!NamingStyles[SK])
return;
const NamingStyle &Style = *NamingStyles[SK];
StringRef Name = Decl->getName();
if (matchesStyle(Name, Style))
return;
std::string KindName = fixupWithCase(StyleNames[SK], CT_LowerCase);
std::replace(KindName.begin(), KindName.end(), '_', ' ');
std::string Fixup = fixupWithStyle(Name, Style);
if (StringRef(Fixup).equals(Name)) {
if (!IgnoreFailedSplit) {
LLVM_DEBUG(llvm::dbgs()
<< Decl->getBeginLoc().printToString(*Result.SourceManager)
<< llvm::format(": unable to split words for %s '%s'\n",
KindName.c_str(), Name.str().c_str()));
}
} else {
NamingCheckFailure &Failure = NamingCheckFailures[NamingCheckId(
Decl->getLocation(), Decl->getNameAsString())];
SourceRange Range =
DeclarationNameInfo(Decl->getDeclName(), Decl->getLocation())
.getSourceRange();
const IdentifierTable &Idents = Decl->getASTContext().Idents;
auto CheckNewIdentifier = Idents.find(Fixup);
if (CheckNewIdentifier != Idents.end()) {
const IdentifierInfo *Ident = CheckNewIdentifier->second;
if (Ident->isKeyword(getLangOpts()))
Failure.FixStatus = ShouldFixStatus::ConflictsWithKeyword;
else if (Ident->hasMacroDefinition())
Failure.FixStatus = ShouldFixStatus::ConflictsWithMacroDefinition;
}
Failure.Fixup = std::move(Fixup);
Failure.KindName = std::move(KindName);
addUsage(NamingCheckFailures, Decl, Range);
}
}
}
void IdentifierNamingCheck::checkMacro(SourceManager &SourceMgr,
const Token &MacroNameTok,
const MacroInfo *MI) {
llvm::Optional<RenamerClangTidyCheck::FailureInfo>
IdentifierNamingCheck::GetMacroFailureInfo(const Token &MacroNameTok,
const SourceManager &SM) const {
if (!NamingStyles[SK_MacroDefinition])
return;
return None;
StringRef Name = MacroNameTok.getIdentifierInfo()->getName();
const NamingStyle &Style = *NamingStyles[SK_MacroDefinition];
if (matchesStyle(Name, Style))
return;
return None;
std::string KindName =
fixupWithCase(StyleNames[SK_MacroDefinition], CT_LowerCase);
@ -929,74 +662,22 @@ void IdentifierNamingCheck::checkMacro(SourceManager &SourceMgr,
if (StringRef(Fixup).equals(Name)) {
if (!IgnoreFailedSplit) {
LLVM_DEBUG(llvm::dbgs()
<< MacroNameTok.getLocation().printToString(SourceMgr)
<< MacroNameTok.getLocation().printToString(SM)
<< llvm::format(": unable to split words for %s '%s'\n",
KindName.c_str(), Name.str().c_str()));
}
} else {
NamingCheckId ID(MI->getDefinitionLoc(), Name);
NamingCheckFailure &Failure = NamingCheckFailures[ID];
SourceRange Range(MacroNameTok.getLocation(), MacroNameTok.getEndLoc());
Failure.Fixup = std::move(Fixup);
Failure.KindName = std::move(KindName);
addUsage(NamingCheckFailures, ID, Range);
return None;
}
return FailureInfo{std::move(KindName), std::move(Fixup)};
}
void IdentifierNamingCheck::expandMacro(const Token &MacroNameTok,
const MacroInfo *MI) {
StringRef Name = MacroNameTok.getIdentifierInfo()->getName();
NamingCheckId ID(MI->getDefinitionLoc(), Name);
auto Failure = NamingCheckFailures.find(ID);
if (Failure == NamingCheckFailures.end())
return;
SourceRange Range(MacroNameTok.getLocation(), MacroNameTok.getEndLoc());
addUsage(NamingCheckFailures, ID, Range);
}
void IdentifierNamingCheck::onEndOfTranslationUnit() {
for (const auto &Pair : NamingCheckFailures) {
const NamingCheckId &Decl = Pair.first;
const NamingCheckFailure &Failure = Pair.second;
if (Failure.KindName.empty())
continue;
if (Failure.ShouldNotify()) {
auto Diag =
diag(Decl.first,
"invalid case style for %0 '%1'%select{|" // Case 0 is empty on
// purpose, because we
// intent to provide a
// fix
"; cannot be fixed because '%3' would conflict with a keyword|"
"; cannot be fixed because '%3' would conflict with a macro "
"definition}2")
<< Failure.KindName << Decl.second
<< static_cast<int>(Failure.FixStatus) << Failure.Fixup;
if (Failure.ShouldFix()) {
for (const auto &Loc : Failure.RawUsageLocs) {
// We assume that the identifier name is made of one token only. This
// is always the case as we ignore usages in macros that could build
// identifier names by combining multiple tokens.
//
// For destructors, we already take care of it by remembering the
// location of the start of the identifier and not the start of the
// tilde.
//
// Other multi-token identifiers, such as operators are not checked at
// all.
Diag << FixItHint::CreateReplacement(
SourceRange(SourceLocation::getFromRawEncoding(Loc)),
Failure.Fixup);
}
}
}
}
RenamerClangTidyCheck::DiagInfo
IdentifierNamingCheck::GetDiagInfo(const NamingCheckId &ID,
const NamingCheckFailure &Failure) const {
return DiagInfo{"invalid case style for %0 '%1'",
[&](DiagnosticBuilder &diag) {
diag << Failure.Info.KindName << ID.second;
}};
}
} // namespace readability

View File

@ -9,8 +9,7 @@
#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_READABILITY_IDENTIFIERNAMINGCHECK_H
#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_READABILITY_IDENTIFIERNAMINGCHECK_H
#include "../ClangTidyCheck.h"
#include "../utils/RenamerClangTidyCheck.h"
namespace clang {
class MacroInfo;
@ -31,17 +30,12 @@ namespace readability {
/// different rules for different kind of identifier. In general, the
/// rules are falling back to a more generic rule if the specific case is not
/// configured.
class IdentifierNamingCheck : public ClangTidyCheck {
class IdentifierNamingCheck final : public RenamerClangTidyCheck {
public:
IdentifierNamingCheck(StringRef Name, ClangTidyContext *Context);
~IdentifierNamingCheck();
void storeOptions(ClangTidyOptions::OptionMap &Opts) override;
void registerMatchers(ast_matchers::MatchFinder *Finder) override;
void check(const ast_matchers::MatchFinder::MatchResult &Result) override;
void registerPPCallbacks(const SourceManager &SM, Preprocessor *PP,
Preprocessor *ModuleExpanderPP) override;
void onEndOfTranslationUnit() override;
enum CaseType {
CT_AnyCase = 0,
@ -65,66 +59,18 @@ public:
std::string Suffix;
};
/// This enum will be used in %select of the diagnostic message.
/// Each value below IgnoreFailureThreshold should have an error message.
enum class ShouldFixStatus {
ShouldFix,
ConflictsWithKeyword, /// The fixup will conflict with a language keyword,
/// so we can't fix it automatically.
ConflictsWithMacroDefinition, /// The fixup will conflict with a macro
/// definition, so we can't fix it
/// automatically.
/// Values pass this threshold will be ignored completely
/// i.e no message, no fixup.
IgnoreFailureThreshold,
InsideMacro, /// If the identifier was used or declared within a macro we
/// won't offer a fixup for safety reasons.
};
/// Holds an identifier name check failure, tracking the kind of the
/// identifier, its possible fixup and the starting locations of all the
/// identifier usages.
struct NamingCheckFailure {
std::string KindName;
std::string Fixup;
/// Whether the failure should be fixed or not.
///
/// ie: if the identifier was used or declared within a macro we won't offer
/// a fixup for safety reasons.
bool ShouldFix() const { return FixStatus == ShouldFixStatus::ShouldFix; }
bool ShouldNotify() const {
return FixStatus < ShouldFixStatus::IgnoreFailureThreshold;
}
ShouldFixStatus FixStatus = ShouldFixStatus::ShouldFix;
/// A set of all the identifier usages starting SourceLocation, in
/// their encoded form.
llvm::DenseSet<unsigned> RawUsageLocs;
NamingCheckFailure() = default;
};
typedef std::pair<SourceLocation, std::string> NamingCheckId;
typedef llvm::DenseMap<NamingCheckId, NamingCheckFailure>
NamingCheckFailureMap;
/// Check Macros for style violations.
void checkMacro(SourceManager &sourceMgr, const Token &MacroNameTok,
const MacroInfo *MI);
/// Add a usage of a macro if it already has a violation.
void expandMacro(const Token &MacroNameTok, const MacroInfo *MI);
private:
llvm::Optional<FailureInfo>
GetDeclFailureInfo(const NamedDecl *Decl,
const SourceManager &SM) const override;
llvm::Optional<FailureInfo>
GetMacroFailureInfo(const Token &MacroNameTok,
const SourceManager &SM) const override;
DiagInfo GetDiagInfo(const NamingCheckId &ID,
const NamingCheckFailure &Failure) const override;
std::vector<llvm::Optional<NamingStyle>> NamingStyles;
bool IgnoreFailedSplit;
NamingCheckFailureMap NamingCheckFailures;
};
} // namespace readability

View File

@ -13,6 +13,7 @@ add_clang_library(clangTidyUtils
LexerUtils.cpp
NamespaceAliaser.cpp
OptionsUtils.cpp
RenamerClangTidyCheck.cpp
TransformerClangTidyCheck.cpp
TypeTraits.cpp
UsingInserter.cpp

View File

@ -0,0 +1,422 @@
//===--- RenamerClangTidyCheck.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 "RenamerClangTidyCheck.h"
#include "ASTUtils.h"
#include "clang/AST/CXXInheritance.h"
#include "clang/ASTMatchers/ASTMatchFinder.h"
#include "clang/Frontend/CompilerInstance.h"
#include "clang/Lex/PPCallbacks.h"
#include "clang/Lex/Preprocessor.h"
#include "llvm/ADT/DenseMapInfo.h"
#include "llvm/Support/Debug.h"
#include "llvm/Support/Format.h"
#define DEBUG_TYPE "clang-tidy"
using namespace clang::ast_matchers;
namespace llvm {
/// Specialisation of DenseMapInfo to allow NamingCheckId objects in DenseMaps
template <>
struct DenseMapInfo<clang::tidy::RenamerClangTidyCheck::NamingCheckId> {
using NamingCheckId = clang::tidy::RenamerClangTidyCheck::NamingCheckId;
static inline NamingCheckId getEmptyKey() {
return NamingCheckId(
clang::SourceLocation::getFromRawEncoding(static_cast<unsigned>(-1)),
"EMPTY");
}
static inline NamingCheckId getTombstoneKey() {
return NamingCheckId(
clang::SourceLocation::getFromRawEncoding(static_cast<unsigned>(-2)),
"TOMBSTONE");
}
static unsigned getHashValue(NamingCheckId Val) {
assert(Val != getEmptyKey() && "Cannot hash the empty key!");
assert(Val != getTombstoneKey() && "Cannot hash the tombstone key!");
std::hash<NamingCheckId::second_type> SecondHash;
return Val.first.getRawEncoding() + SecondHash(Val.second);
}
static bool isEqual(const NamingCheckId &LHS, const NamingCheckId &RHS) {
if (RHS == getEmptyKey())
return LHS == getEmptyKey();
if (RHS == getTombstoneKey())
return LHS == getTombstoneKey();
return LHS == RHS;
}
};
} // namespace llvm
namespace clang {
namespace tidy {
namespace {
/// Callback supplies macros to RenamerClangTidyCheck::checkMacro
class RenamerClangTidyCheckPPCallbacks : public PPCallbacks {
public:
RenamerClangTidyCheckPPCallbacks(Preprocessor *PP,
RenamerClangTidyCheck *Check)
: PP(PP), Check(Check) {}
/// MacroDefined calls checkMacro for macros in the main file
void MacroDefined(const Token &MacroNameTok,
const MacroDirective *MD) override {
Check->checkMacro(PP->getSourceManager(), MacroNameTok, MD->getMacroInfo());
}
/// MacroExpands calls expandMacro for macros in the main file
void MacroExpands(const Token &MacroNameTok, const MacroDefinition &MD,
SourceRange /*Range*/,
const MacroArgs * /*Args*/) override {
Check->expandMacro(MacroNameTok, MD.getMacroInfo());
}
private:
Preprocessor *PP;
RenamerClangTidyCheck *Check;
};
} // namespace
RenamerClangTidyCheck::RenamerClangTidyCheck(StringRef CheckName,
ClangTidyContext *Context)
: ClangTidyCheck(CheckName, Context) {}
RenamerClangTidyCheck::~RenamerClangTidyCheck() = default;
void RenamerClangTidyCheck::registerMatchers(MatchFinder *Finder) {
Finder->addMatcher(namedDecl().bind("decl"), this);
Finder->addMatcher(usingDecl().bind("using"), this);
Finder->addMatcher(declRefExpr().bind("declRef"), this);
Finder->addMatcher(cxxConstructorDecl(unless(isImplicit())).bind("classRef"),
this);
Finder->addMatcher(cxxDestructorDecl(unless(isImplicit())).bind("classRef"),
this);
Finder->addMatcher(typeLoc().bind("typeLoc"), this);
Finder->addMatcher(nestedNameSpecifierLoc().bind("nestedNameLoc"), this);
Finder->addMatcher(
functionDecl(unless(cxxMethodDecl(isImplicit())),
hasBody(forEachDescendant(memberExpr().bind("memberExpr")))),
this);
Finder->addMatcher(
cxxConstructorDecl(
unless(isImplicit()),
forEachConstructorInitializer(
allOf(isWritten(), withInitializer(forEachDescendant(
memberExpr().bind("memberExpr")))))),
this);
Finder->addMatcher(fieldDecl(hasInClassInitializer(
forEachDescendant(memberExpr().bind("memberExpr")))),
this);
}
void RenamerClangTidyCheck::registerPPCallbacks(
const SourceManager &SM, Preprocessor *PP, Preprocessor *ModuleExpanderPP) {
ModuleExpanderPP->addPPCallbacks(
std::make_unique<RenamerClangTidyCheckPPCallbacks>(ModuleExpanderPP,
this));
}
static void addUsage(RenamerClangTidyCheck::NamingCheckFailureMap &Failures,
const RenamerClangTidyCheck::NamingCheckId &Decl,
SourceRange Range, SourceManager *SourceMgr = nullptr) {
// Do nothing if the provided range is invalid.
if (Range.getBegin().isInvalid() || Range.getEnd().isInvalid())
return;
// If we have a source manager, use it to convert to the spelling location for
// performing the fix. This is necessary because macros can map the same
// spelling location to different source locations, and we only want to fix
// the token once, before it is expanded by the macro.
SourceLocation FixLocation = Range.getBegin();
if (SourceMgr)
FixLocation = SourceMgr->getSpellingLoc(FixLocation);
if (FixLocation.isInvalid())
return;
// Try to insert the identifier location in the Usages map, and bail out if it
// is already in there
RenamerClangTidyCheck::NamingCheckFailure &Failure = Failures[Decl];
if (!Failure.RawUsageLocs.insert(FixLocation.getRawEncoding()).second)
return;
if (!Failure.ShouldFix())
return;
if (!utils::rangeCanBeFixed(Range, SourceMgr))
Failure.FixStatus = RenamerClangTidyCheck::ShouldFixStatus::InsideMacro;
}
/// Convenience method when the usage to be added is a NamedDecl
static void addUsage(RenamerClangTidyCheck::NamingCheckFailureMap &Failures,
const NamedDecl *Decl, SourceRange Range,
SourceManager *SourceMgr = nullptr) {
return addUsage(Failures,
RenamerClangTidyCheck::NamingCheckId(Decl->getLocation(),
Decl->getNameAsString()),
Range, SourceMgr);
}
void RenamerClangTidyCheck::check(const MatchFinder::MatchResult &Result) {
if (const auto *Decl =
Result.Nodes.getNodeAs<CXXConstructorDecl>("classRef")) {
addUsage(NamingCheckFailures, Decl->getParent(),
Decl->getNameInfo().getSourceRange());
for (const auto *Init : Decl->inits()) {
if (!Init->isWritten() || Init->isInClassMemberInitializer())
continue;
if (const FieldDecl *FD = Init->getAnyMember())
addUsage(NamingCheckFailures, FD,
SourceRange(Init->getMemberLocation()));
// Note: delegating constructors and base class initializers are handled
// via the "typeLoc" matcher.
}
return;
}
if (const auto *Decl =
Result.Nodes.getNodeAs<CXXDestructorDecl>("classRef")) {
SourceRange Range = Decl->getNameInfo().getSourceRange();
if (Range.getBegin().isInvalid())
return;
// The first token that will be found is the ~ (or the equivalent trigraph),
// we want instead to replace the next token, that will be the identifier.
Range.setBegin(CharSourceRange::getTokenRange(Range).getEnd());
addUsage(NamingCheckFailures, Decl->getParent(), Range);
return;
}
if (const auto *Loc = Result.Nodes.getNodeAs<TypeLoc>("typeLoc")) {
NamedDecl *Decl = nullptr;
if (const auto &Ref = Loc->getAs<TagTypeLoc>())
Decl = Ref.getDecl();
else if (const auto &Ref = Loc->getAs<InjectedClassNameTypeLoc>())
Decl = Ref.getDecl();
else if (const auto &Ref = Loc->getAs<UnresolvedUsingTypeLoc>())
Decl = Ref.getDecl();
else if (const auto &Ref = Loc->getAs<TemplateTypeParmTypeLoc>())
Decl = Ref.getDecl();
// further TypeLocs handled below
if (Decl) {
addUsage(NamingCheckFailures, Decl, Loc->getSourceRange());
return;
}
if (const auto &Ref = Loc->getAs<TemplateSpecializationTypeLoc>()) {
const TemplateDecl *Decl =
Ref.getTypePtr()->getTemplateName().getAsTemplateDecl();
SourceRange Range(Ref.getTemplateNameLoc(), Ref.getTemplateNameLoc());
if (const auto *ClassDecl = dyn_cast<TemplateDecl>(Decl)) {
if (const NamedDecl *TemplDecl = ClassDecl->getTemplatedDecl())
addUsage(NamingCheckFailures, TemplDecl, Range);
return;
}
}
if (const auto &Ref =
Loc->getAs<DependentTemplateSpecializationTypeLoc>()) {
if (const TagDecl *Decl = Ref.getTypePtr()->getAsTagDecl())
addUsage(NamingCheckFailures, Decl, Loc->getSourceRange());
return;
}
}
if (const auto *Loc =
Result.Nodes.getNodeAs<NestedNameSpecifierLoc>("nestedNameLoc")) {
if (const NestedNameSpecifier *Spec = Loc->getNestedNameSpecifier()) {
if (const NamespaceDecl *Decl = Spec->getAsNamespace()) {
addUsage(NamingCheckFailures, Decl, Loc->getLocalSourceRange());
return;
}
}
}
if (const auto *Decl = Result.Nodes.getNodeAs<UsingDecl>("using")) {
for (const auto *Shadow : Decl->shadows())
addUsage(NamingCheckFailures, Shadow->getTargetDecl(),
Decl->getNameInfo().getSourceRange());
return;
}
if (const auto *DeclRef = Result.Nodes.getNodeAs<DeclRefExpr>("declRef")) {
SourceRange Range = DeclRef->getNameInfo().getSourceRange();
addUsage(NamingCheckFailures, DeclRef->getDecl(), Range,
Result.SourceManager);
return;
}
if (const auto *MemberRef =
Result.Nodes.getNodeAs<MemberExpr>("memberExpr")) {
SourceRange Range = MemberRef->getMemberNameInfo().getSourceRange();
addUsage(NamingCheckFailures, MemberRef->getMemberDecl(), Range,
Result.SourceManager);
return;
}
if (const auto *Decl = Result.Nodes.getNodeAs<NamedDecl>("decl")) {
if (!Decl->getIdentifier() || Decl->getName().empty() || Decl->isImplicit())
return;
// Fix type aliases in value declarations.
if (const auto *Value = Result.Nodes.getNodeAs<ValueDecl>("decl")) {
if (const Type *TypePtr = Value->getType().getTypePtrOrNull()) {
if (const auto *Typedef = TypePtr->getAs<TypedefType>())
addUsage(NamingCheckFailures, Typedef->getDecl(),
Value->getSourceRange());
}
}
// Fix type aliases in function declarations.
if (const auto *Value = Result.Nodes.getNodeAs<FunctionDecl>("decl")) {
if (const auto *Typedef =
Value->getReturnType().getTypePtr()->getAs<TypedefType>())
addUsage(NamingCheckFailures, Typedef->getDecl(),
Value->getSourceRange());
for (unsigned i = 0; i < Value->getNumParams(); ++i) {
if (const TypedefType *Typedef = Value->parameters()[i]
->getType()
.getTypePtr()
->getAs<TypedefType>())
addUsage(NamingCheckFailures, Typedef->getDecl(),
Value->getSourceRange());
}
}
// Ignore ClassTemplateSpecializationDecl which are creating duplicate
// replacements with CXXRecordDecl.
if (isa<ClassTemplateSpecializationDecl>(Decl))
return;
Optional<FailureInfo> MaybeFailure =
GetDeclFailureInfo(Decl, *Result.SourceManager);
if (!MaybeFailure)
return;
FailureInfo &Info = *MaybeFailure;
NamingCheckFailure &Failure = NamingCheckFailures[NamingCheckId(
Decl->getLocation(), Decl->getNameAsString())];
SourceRange Range =
DeclarationNameInfo(Decl->getDeclName(), Decl->getLocation())
.getSourceRange();
const IdentifierTable &Idents = Decl->getASTContext().Idents;
auto CheckNewIdentifier = Idents.find(Info.Fixup);
if (CheckNewIdentifier != Idents.end()) {
const IdentifierInfo *Ident = CheckNewIdentifier->second;
if (Ident->isKeyword(getLangOpts()))
Failure.FixStatus = ShouldFixStatus::ConflictsWithKeyword;
else if (Ident->hasMacroDefinition())
Failure.FixStatus = ShouldFixStatus::ConflictsWithMacroDefinition;
}
Failure.Info = std::move(Info);
addUsage(NamingCheckFailures, Decl, Range);
}
}
void RenamerClangTidyCheck::checkMacro(SourceManager &SourceMgr,
const Token &MacroNameTok,
const MacroInfo *MI) {
Optional<FailureInfo> MaybeFailure =
GetMacroFailureInfo(MacroNameTok, SourceMgr);
if (!MaybeFailure)
return;
FailureInfo &Info = *MaybeFailure;
StringRef Name = MacroNameTok.getIdentifierInfo()->getName();
NamingCheckId ID(MI->getDefinitionLoc(), Name);
NamingCheckFailure &Failure = NamingCheckFailures[ID];
SourceRange Range(MacroNameTok.getLocation(), MacroNameTok.getEndLoc());
Failure.Info = std::move(Info);
addUsage(NamingCheckFailures, ID, Range);
}
void RenamerClangTidyCheck::expandMacro(const Token &MacroNameTok,
const MacroInfo *MI) {
StringRef Name = MacroNameTok.getIdentifierInfo()->getName();
NamingCheckId ID(MI->getDefinitionLoc(), Name);
auto Failure = NamingCheckFailures.find(ID);
if (Failure == NamingCheckFailures.end())
return;
SourceRange Range(MacroNameTok.getLocation(), MacroNameTok.getEndLoc());
addUsage(NamingCheckFailures, ID, Range);
}
static std::string
getDiagnosticSuffix(const RenamerClangTidyCheck::ShouldFixStatus FixStatus,
const std::string &Fixup) {
if (Fixup.empty())
return "; cannot be fixed automatically";
if (FixStatus == RenamerClangTidyCheck::ShouldFixStatus::ShouldFix)
return {};
if (FixStatus >=
RenamerClangTidyCheck::ShouldFixStatus::IgnoreFailureThreshold)
return {};
if (FixStatus == RenamerClangTidyCheck::ShouldFixStatus::ConflictsWithKeyword)
return "; cannot be fixed because '" + Fixup +
"' would conflict with a keyword";
if (FixStatus ==
RenamerClangTidyCheck::ShouldFixStatus::ConflictsWithMacroDefinition)
return "; cannot be fixed because '" + Fixup +
"' would conflict with a macro definition";
llvm_unreachable("invalid ShouldFixStatus");
}
void RenamerClangTidyCheck::onEndOfTranslationUnit() {
for (const auto &Pair : NamingCheckFailures) {
const NamingCheckId &Decl = Pair.first;
const NamingCheckFailure &Failure = Pair.second;
if (Failure.Info.KindName.empty())
continue;
if (Failure.ShouldNotify()) {
auto DiagInfo = GetDiagInfo(Decl, Failure);
auto Diag = diag(Decl.first,
DiagInfo.Text + getDiagnosticSuffix(Failure.FixStatus,
Failure.Info.Fixup));
DiagInfo.ApplyArgs(Diag);
if (Failure.ShouldFix()) {
for (const auto &Loc : Failure.RawUsageLocs) {
// We assume that the identifier name is made of one token only. This
// is always the case as we ignore usages in macros that could build
// identifier names by combining multiple tokens.
//
// For destructors, we already take care of it by remembering the
// location of the start of the identifier and not the start of the
// tilde.
//
// Other multi-token identifiers, such as operators are not checked at
// all.
Diag << FixItHint::CreateReplacement(
SourceRange(SourceLocation::getFromRawEncoding(Loc)),
Failure.Info.Fixup);
}
}
}
}
}
} // namespace tidy
} // namespace clang

View File

@ -0,0 +1,150 @@
//===--- RenamderClangTidyCheck.h - clang-tidy ------------------*- C++ -*-===//
//
// 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
//
//===----------------------------------------------------------------------===//
#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_RENAMERCLANGTIDYCHECK_H
#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_RENAMERCLANGTIDYCHECK_H
#include "../ClangTidyCheck.h"
#include "llvm/ADT/DenseMap.h"
#include "llvm/ADT/DenseSet.h"
#include "llvm/ADT/Optional.h"
#include <string>
#include <utility>
namespace clang {
class MacroInfo;
namespace tidy {
/// Base class for clang-tidy checks that want to flag declarations and/or
/// macros for renaming based on customizable criteria.
class RenamerClangTidyCheck : public ClangTidyCheck {
public:
RenamerClangTidyCheck(StringRef CheckName, ClangTidyContext *Context);
~RenamerClangTidyCheck();
/// Derived classes should not implement any matching logic themselves; this
/// class will do the matching and call the derived class'
/// GetDeclFailureInfo() and GetMacroFailureInfo() for determining whether a
/// given identifier passes or fails the check.
void registerMatchers(ast_matchers::MatchFinder *Finder) override final;
void
check(const ast_matchers::MatchFinder::MatchResult &Result) override final;
void registerPPCallbacks(const SourceManager &SM, Preprocessor *PP,
Preprocessor *ModuleExpanderPP) override final;
void onEndOfTranslationUnit() override final;
/// This enum will be used in %select of the diagnostic message.
/// Each value below IgnoreFailureThreshold should have an error message.
enum class ShouldFixStatus {
ShouldFix,
/// The fixup will conflict with a language keyword,
/// so we can't fix it automatically.
ConflictsWithKeyword,
/// The fixup will conflict with a macro
/// definition, so we can't fix it
/// automatically.
ConflictsWithMacroDefinition,
/// Values pass this threshold will be ignored completely
/// i.e no message, no fixup.
IgnoreFailureThreshold,
/// If the identifier was used or declared within a macro we
/// won't offer a fixup for safety reasons.
InsideMacro,
};
/// Information describing a failed check
struct FailureInfo {
std::string KindName; // Tag or misc info to be used as derived classes need
std::string Fixup; // The name that will be proposed as a fix-it hint
};
/// Holds an identifier name check failure, tracking the kind of the
/// identifier, its possible fixup and the starting locations of all the
/// identifier usages.
struct NamingCheckFailure {
FailureInfo Info;
/// Whether the failure should be fixed or not.
///
/// e.g.: if the identifier was used or declared within a macro we won't
/// offer a fixup for safety reasons.
bool ShouldFix() const {
return FixStatus == ShouldFixStatus::ShouldFix && !Info.Fixup.empty();
}
bool ShouldNotify() const {
return FixStatus < ShouldFixStatus::IgnoreFailureThreshold;
}
ShouldFixStatus FixStatus = ShouldFixStatus::ShouldFix;
/// A set of all the identifier usages starting SourceLocation, in
/// their encoded form.
llvm::DenseSet<unsigned> RawUsageLocs;
NamingCheckFailure() = default;
};
using NamingCheckId = std::pair<SourceLocation, std::string>;
using NamingCheckFailureMap =
llvm::DenseMap<NamingCheckId, NamingCheckFailure>;
/// Check Macros for style violations.
void checkMacro(SourceManager &sourceMgr, const Token &MacroNameTok,
const MacroInfo *MI);
/// Add a usage of a macro if it already has a violation.
void expandMacro(const Token &MacroNameTok, const MacroInfo *MI);
protected:
/// Overridden by derived classes, returns information about if and how a Decl
/// failed the check. A 'None' result means the Decl did not fail the check.
virtual llvm::Optional<FailureInfo>
GetDeclFailureInfo(const NamedDecl *Decl, const SourceManager &SM) const = 0;
/// Overridden by derived classes, returns information about if and how a
/// macro failed the check. A 'None' result means the macro did not fail the
/// check.
virtual llvm::Optional<FailureInfo>
GetMacroFailureInfo(const Token &MacroNameTok,
const SourceManager &SM) const = 0;
/// Represents customized diagnostic text and how arguments should be applied.
/// Example usage:
///
/// return DiagInfo{"my %1 very %2 special %3 text",
/// [=](DiagnosticBuilder &diag) {
/// diag << arg1 << arg2 << arg3;
/// }};
struct DiagInfo {
std::string Text;
llvm::unique_function<void(DiagnosticBuilder &)> ApplyArgs;
};
/// Overridden by derived classes, returns a description of the diagnostic
/// that should be emitted for the given failure. The base class will then
/// further customize the diagnostic by adding info about whether the fix-it
/// can be automatically applied or not.
virtual DiagInfo GetDiagInfo(const NamingCheckId &ID,
const NamingCheckFailure &Failure) const = 0;
private:
NamingCheckFailureMap NamingCheckFailures;
};
} // namespace tidy
} // namespace clang
#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_RENAMERCLANGTIDYCHECK_H