blob: 65111254c44a38324f3eb5684a6304bdef20dd37 [file] [log] [blame]
//===--- RemoveUsingNamespace.cpp --------------------------------*- 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
//
//===----------------------------------------------------------------------===//
#include "AST.h"
#include "FindTarget.h"
#include "Selection.h"
#include "SourceCode.h"
#include "refactor/Tweak.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 "clang/Tooling/Refactoring/RecursiveSymbolVisitor.h"
#include "llvm/ADT/ScopeExit.h"
namespace clang {
namespace clangd {
namespace {
/// Removes the 'using namespace' under the cursor and qualifies all accesses in
/// the current file. E.g.,
/// using namespace std;
/// vector<int> foo(std::map<int, int>);
/// Would become:
/// std::vector<int> foo(std::map<int, int>);
/// Currently limited to using namespace directives inside global namespace to
/// simplify implementation. Also the namespace must not contain using
/// directives.
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;
Intent intent() const override { return Refactor; }
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;
};
/// Produce edit removing 'using namespace xxx::yyy' and the trailing semicolon.
llvm::Expected<tooling::Replacement>
removeUsingDirective(ASTContext &Ctx, const UsingDirectiveDecl *D) {
auto &SM = Ctx.getSourceManager();
llvm::Optional<Token> NextTok =
Lexer::findNextToken(D->getEndLoc(), SM, Ctx.getLangOpts());
if (!NextTok || NextTok->isNot(tok::semi))
return llvm::createStringError(llvm::inconvertibleErrorCode(),
"no semicolon after using-directive");
// FIXME: removing the semicolon may be invalid in some obscure cases, e.g.
// if (x) using namespace std; else using namespace bar;
return tooling::Replacement(
SM,
CharSourceRange::getTokenRange(D->getBeginLoc(), NextTok->getLocation()),
"", Ctx.getLangOpts());
}
// Returns true iff the parent of the Node is a TUDecl.
bool isTopLevelDecl(const SelectionTree::Node *Node) {
return Node->Parent && Node->Parent->ASTNode.get<TranslationUnitDecl>();
}
// Returns the first visible context that contains this DeclContext.
// For example: Returns ns1 for S1 and a.
// namespace ns1 {
// inline namespace ns2 { struct S1 {}; }
// enum E { a, b, c, d };
// }
const DeclContext *visibleContext(const DeclContext *D) {
while (D->isInlineNamespace() || D->isTransparentContext())
D = D->getParent();
return D;
}
bool RemoveUsingNamespace::prepare(const Selection &Inputs) {
// Find the 'using namespace' directive under the cursor.
auto *CA = Inputs.ASTSelection.commonAncestor();
if (!CA)
return false;
TargetDirective = CA->ASTNode.get<UsingDirectiveDecl>();
if (!TargetDirective)
return false;
if (!dyn_cast<Decl>(TargetDirective->getDeclContext()))
return false;
// FIXME: Unavailable for namespaces containing using-namespace decl.
// It is non-trivial to deal with cases where identifiers come from the inner
// namespace. For example map has to be changed to aa::map.
// namespace aa {
// namespace bb { struct map {}; }
// using namespace bb;
// }
// using namespace a^a;
// int main() { map m; }
// We need to make this aware of the transitive using-namespace decls.
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();
// First, collect *all* using namespace directives that redeclare the same
// namespace.
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();
}
// Collect all references to symbols from the namespace for which we're
// removing the directive.
std::vector<SourceLocation> IdentsToQualify;
for (auto &D : Inputs.AST.getLocalTopLevelDecls()) {
findExplicitReferences(D, [&](ReferenceLoc Ref) {
if (Ref.Qualifier)
return; // This reference is already qualified.
for (auto *T : Ref.Targets) {
if (!visibleContext(T->getDeclContext())
->Equals(TargetDirective->getNominatedNamespace()))
return;
}
SourceLocation Loc = Ref.NameLoc;
if (Loc.isMacroID()) {
// Avoid adding qualifiers before macro expansions, it's probably
// incorrect, e.g.
// namespace std { int foo(); }
// #define FOO 1 + foo()
// using namespace foo; // provides matrix
// auto x = FOO; // Must not changed to auto x = std::FOO
if (!SM.isMacroArgExpansion(Loc))
return; // FIXME: report a warning to the users.
Loc = SM.getFileLoc(Ref.NameLoc);
}
assert(Loc.isFileID());
if (SM.getFileID(Loc) != SM.getMainFileID())
return; // FIXME: report these to the user as warnings?
if (SM.isBeforeInTranslationUnit(Loc, FirstUsingDirectiveLoc))
return; // Directive was not visible before this point.
IdentsToQualify.push_back(Loc);
});
}
// Remove duplicates.
llvm::sort(IdentsToQualify);
IdentsToQualify.erase(
std::unique(IdentsToQualify.begin(), IdentsToQualify.end()),
IdentsToQualify.end());
// Produce replacements to remove the using directives.
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);
}
// Produce replacements to add the qualifiers.
std::string Qualifier = printUsingNamespaceName(Ctx, *TargetDirective) + "::";
for (auto Loc : IdentsToQualify) {
if (auto Err = R.add(tooling::Replacement(Ctx.getSourceManager(), Loc,
/*Length=*/0, Qualifier)))
return std::move(Err);
}
return Effect::mainFileEdit(SM, std::move(R));
}
std::string RemoveUsingNamespace::title() const {
return llvm::formatv("Remove using namespace, re-qualify names instead.");
}
} // namespace
} // namespace clangd
} // namespace clang