#include "AST.h"
#include "FindTarget.h"
#include "Selection.h"
#include "refactor/Tweak.h"
#include "support/Logger.h"
#include "clang/AST/Decl.h"
#include "clang/AST/DeclBase.h"
#include "clang/AST/DeclCXX.h"
#include "clang/AST/RecursiveASTVisitor.h"
#include "clang/Basic/SourceLocation.h"
#include "clang/Tooling/Core/Replacement.h"
#include <optional>
namespace clang {
namespace clangd {
namespace {
class RemoveUsingNamespace : public Tweak {
public:
const char *id() const override;
bool prepare(const Selection &Inputs) override;
Expected<Effect> apply(const Selection &Inputs) override;
std::string title() const override {
return "Remove using namespace, re-qualify names instead";
}
llvm::StringLiteral kind() const override {
return CodeAction::REFACTOR_KIND;
}
private:
const UsingDirectiveDecl *TargetDirective = nullptr;
};
REGISTER_TWEAK(RemoveUsingNamespace)
class FindSameUsings : public RecursiveASTVisitor<FindSameUsings> {
public:
FindSameUsings(const UsingDirectiveDecl &Target,
std::vector<const UsingDirectiveDecl *> &Results)
: TargetNS(Target.getNominatedNamespace()),
TargetCtx(Target.getDeclContext()), Results(Results) {}
bool VisitUsingDirectiveDecl(UsingDirectiveDecl *D) {
if (D->getNominatedNamespace() != TargetNS ||
D->getDeclContext() != TargetCtx)
return true;
Results.push_back(D);
return true;
}
private:
const NamespaceDecl *TargetNS;
const DeclContext *TargetCtx;
std::vector<const UsingDirectiveDecl *> &Results;
};
llvm::Expected<tooling::Replacement>
removeUsingDirective(ASTContext &Ctx, const UsingDirectiveDecl *D) {
auto &SM = Ctx.getSourceManager();
std::optional<Token> NextTok =
Lexer::findNextToken(D->getEndLoc(), SM, Ctx.getLangOpts());
if (!NextTok || NextTok->isNot(tok::semi))
return error("no semicolon after using-directive");
return tooling::Replacement(
SM,
CharSourceRange::getTokenRange(D->getBeginLoc(), NextTok->getLocation()),
"", Ctx.getLangOpts());
}
bool isTopLevelDecl(const SelectionTree::Node *Node) {
return Node->Parent && Node->Parent->ASTNode.get<TranslationUnitDecl>();
}
bool isDeclaredIn(const NamedDecl *LHS, const DeclContext *RHS) {
const auto *D = LHS->getDeclContext();
while (D->isInlineNamespace() || D->isTransparentContext()) {
if (D->Equals(RHS))
return true;
D = D->getParent();
}
return D->Equals(RHS);
}
bool RemoveUsingNamespace::prepare(const Selection &Inputs) {
auto *CA = Inputs.ASTSelection.commonAncestor();
if (!CA)
return false;
TargetDirective = CA->ASTNode.get<UsingDirectiveDecl>();
if (!TargetDirective)
return false;
if (!isa<Decl>(TargetDirective->getDeclContext()))
return false;
if (!TargetDirective->getNominatedNamespace()->using_directives().empty())
return false;
return isTopLevelDecl(CA);
}
Expected<Tweak::Effect> RemoveUsingNamespace::apply(const Selection &Inputs) {
auto &Ctx = Inputs.AST->getASTContext();
auto &SM = Ctx.getSourceManager();
std::vector<const UsingDirectiveDecl *> AllDirectives;
FindSameUsings(*TargetDirective, AllDirectives).TraverseAST(Ctx);
SourceLocation FirstUsingDirectiveLoc;
for (auto *D : AllDirectives) {
if (FirstUsingDirectiveLoc.isInvalid() ||
SM.isBeforeInTranslationUnit(D->getBeginLoc(), FirstUsingDirectiveLoc))
FirstUsingDirectiveLoc = D->getBeginLoc();
}
std::vector<SourceLocation> IdentsToQualify;
for (auto &D : Inputs.AST->getLocalTopLevelDecls()) {
findExplicitReferences(
D,
[&](ReferenceLoc Ref) {
if (Ref.Qualifier)
return;
for (auto *T : Ref.Targets) {
if (!isDeclaredIn(T, TargetDirective->getNominatedNamespace()))
return;
auto Kind = T->getDeclName().getNameKind();
if (Kind == DeclarationName::CXXOperatorName)
return;
if (Kind == DeclarationName::NameKind::CXXLiteralOperatorName)
return;
}
SourceLocation Loc = Ref.NameLoc;
if (Loc.isMacroID()) {
if (!SM.isMacroArgExpansion(Loc))
return;
Loc = SM.getFileLoc(Ref.NameLoc);
}
assert(Loc.isFileID());
if (SM.getFileID(Loc) != SM.getMainFileID())
return;
if (SM.isBeforeInTranslationUnit(Loc, FirstUsingDirectiveLoc))
return;
IdentsToQualify.push_back(Loc);
},
Inputs.AST->getHeuristicResolver());
}
llvm::sort(IdentsToQualify);
IdentsToQualify.erase(
std::unique(IdentsToQualify.begin(), IdentsToQualify.end()),
IdentsToQualify.end());
tooling::Replacements R;
for (auto *D : AllDirectives) {
auto RemoveUsing = removeUsingDirective(Ctx, D);
if (!RemoveUsing)
return RemoveUsing.takeError();
if (auto Err = R.add(*RemoveUsing))
return std::move(Err);
}
std::string Qualifier = printUsingNamespaceName(Ctx, *TargetDirective) + "::";
for (auto Loc : IdentsToQualify) {
if (auto Err = R.add(tooling::Replacement(Ctx.getSourceManager(), Loc,
0, Qualifier)))
return std::move(Err);
}
return Effect::mainFileEdit(SM, std::move(R));
}
}
}
}