Index: clang-tools-extra/clangd/AST.h =================================================================== --- clang-tools-extra/clangd/AST.h +++ clang-tools-extra/clangd/AST.h @@ -41,6 +41,12 @@ /// Returns the first enclosing namespace scope starting from \p DC. std::string printNamespaceScope(const DeclContext &DC); +/// Returns the name of the namespace inside the 'using namespace' directive, as +/// written in the code. E.g., passing 'using namespace ::std' will result in +/// '::std'. +std::string printUsingNamespaceName(const ASTContext &Ctx, + const UsingDirectiveDecl &D); + /// Prints unqualified name of the decl for the purpose of displaying it to the /// user. Anonymous decls return names of the form "(anonymous {kind})", e.g. /// "(anonymous struct)" or "(anonymous namespace)". Index: clang-tools-extra/clangd/AST.cpp =================================================================== --- clang-tools-extra/clangd/AST.cpp +++ clang-tools-extra/clangd/AST.cpp @@ -76,16 +76,25 @@ return nullptr; } +std::string printUsingNamespaceName(const ASTContext &Ctx, + const UsingDirectiveDecl &D) { + PrintingPolicy PP(Ctx.getLangOpts()); + std::string Name; + llvm::raw_string_ostream Out(Name); + + if (auto *Qual = D.getQualifier()) + Qual->print(Out, PP); + D.getNominatedNamespaceAsWritten()->printName(Out); + return Out.str(); +} + std::string printName(const ASTContext &Ctx, const NamedDecl &ND) { std::string Name; llvm::raw_string_ostream Out(Name); PrintingPolicy PP(Ctx.getLangOpts()); // Handle 'using namespace'. They all have the same name - . if (auto *UD = llvm::dyn_cast(&ND)) { - Out << "using namespace "; - if (auto *Qual = UD->getQualifier()) - Qual->print(Out, PP); - UD->getNominatedNamespaceAsWritten()->printName(Out); + Out << "using namespace " << printUsingNamespaceName(Ctx, *UD); return Out.str(); } ND.getDeclName().print(Out, PP); Index: clang-tools-extra/clangd/refactor/tweaks/CMakeLists.txt =================================================================== --- clang-tools-extra/clangd/refactor/tweaks/CMakeLists.txt +++ clang-tools-extra/clangd/refactor/tweaks/CMakeLists.txt @@ -11,5 +11,6 @@ Dummy.cpp # FIXME: to avoid CMake errors due to empty inputs, remove when a # first tweak lands. QualifyName.cpp + RemoveUsingNamespace.cpp SwapIfBranches.cpp ) Index: clang-tools-extra/clangd/refactor/tweaks/RemoveUsingNamespace.cpp =================================================================== --- /dev/null +++ clang-tools-extra/clangd/refactor/tweaks/RemoveUsingNamespace.cpp @@ -0,0 +1,203 @@ +//===--- 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 "ClangdUnit.h" +#include "SourceCode.h" +#include "refactor/Tweak.h" +#include "clang/AST/RecursiveASTVisitor.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 foo(std::map); +/// Would become: +/// std::vector foo(std::map); +class RemoveUsingNamespace : public Tweak { +public: + TweakID id() const override; + + bool prepare(const Selection &Inputs) override; + Expected apply(const Selection &Inputs) override; + std::string title() const override; + +private: + UsingDirectiveDecl *TargetDirective = nullptr; +}; +REGISTER_TWEAK(RemoveUsingNamespace); + +class FindNodeUnderCursor : public RecursiveASTVisitor { +public: + FindNodeUnderCursor(SourceLocation SearchedLoc, UsingDirectiveDecl *&Result) + : SearchedLoc(SearchedLoc), Result(Result) {} + + bool VisitUsingDirectiveDecl(UsingDirectiveDecl *D) { + if (D->getUsingLoc() != SearchedLoc) + return true; + + Result = D; + return false; + } + +private: + SourceLocation SearchedLoc; + UsingDirectiveDecl *&Result; +}; + +class FindSameUsings : public RecursiveASTVisitor { +public: + FindSameUsings(UsingDirectiveDecl &Target, + std::vector &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: + NamespaceDecl *TargetNS; + DeclContext *TargetCtx; + std::vector &Results; +}; + +class FindIdentsToQualify + : public tooling::RecursiveSymbolVisitor { +public: + FindIdentsToQualify(ASTContext &Ctx, NamespaceDecl &TargetNS, + std::vector &ResultIdents) + : RecursiveSymbolVisitor(Ctx.getSourceManager(), Ctx.getLangOpts()), + Ctx(Ctx), TargetNS(TargetNS), ResultIdents(ResultIdents) {} + + bool visitSymbolOccurrence(const NamedDecl *D, + llvm::ArrayRef NameRanges) { + if (!D || D->getCanonicalDecl() == TargetNS.getCanonicalDecl()) + return true; + if (!D->getDeclName().isIdentifier() || + D->getDeclName().getNameKind() == DeclarationName::CXXOperatorName) + return true; // do not add qualifiers for non-idents, e.g. 'operator+'. + // Check the symbol is unqualified and references something inside our + // namespace. + // FIXME: add a check it's unqualified. + if (!TargetNS.InEnclosingNamespaceSetOf(D->getDeclContext())) + return true; + // FIXME: handle more tricky cases, e.g. we don't need the qualifier if we + // have the using decls for some entities, we might have qualified + // references that need updating too. + for (auto R : NameRanges) { + if (!Ctx.getSourceManager().isWrittenInMainFile(R.getBegin())) + continue; // we can't fix refences outside our file. + // FIXME: this might be a conflict that we need to report. + ResultIdents.push_back(R.getBegin()); + } + return true; + } + + bool TraverseDecl(Decl *D) { + if (!Ctx.getSourceManager().isWrittenInMainFile(D->getLocation()) && + !isa(D)) + return true; // skip decls outside main file. + return RecursiveSymbolVisitor::TraverseDecl(D); + } + +private: + ASTContext &Ctx; + NamespaceDecl &TargetNS; + std::vector &ResultIdents; +}; + +// Produces an edit to remove 'using namespace xxx::yyy' and the trailing +// semicolon. +llvm::Expected +removeUsingDirective(ASTContext &Ctx, UsingDirectiveDecl *D) { + auto &SrcMgr = Ctx.getSourceManager(); + auto R = + Lexer::getAsCharRange(D->getSourceRange(), SrcMgr, Ctx.getLangOpts()); + if (R.isInvalid()) + return llvm::createStringError(llvm::inconvertibleErrorCode(), + "invalid source range for using " + "directive"); + SourceLocation EndLoc = R.getEnd(); + auto NextTok = Lexer::findNextToken(EndLoc, SrcMgr, Ctx.getLangOpts()); + if (!NextTok || NextTok->getKind() != tok::semi) + return llvm::createStringError(llvm::inconvertibleErrorCode(), + "no semicolon after using-directive"); + R.setEnd(NextTok->getEndLoc()); + // 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(SrcMgr, R, "", Ctx.getLangOpts()); +} +} // namespace + // namespace + +bool RemoveUsingNamespace::prepare(const Selection &Inputs) { + auto &Ctx = Inputs.AST.getASTContext(); + // Find the 'using namespace' directive under the cursor. + TargetDirective = nullptr; + FindNodeUnderCursor(Inputs.Cursor, TargetDirective).TraverseAST(Ctx); + if (!TargetDirective) + return false; + auto *Parent = dyn_cast(TargetDirective->getDeclContext()); + if (!Parent) + return false; + return true; +} + +Expected +RemoveUsingNamespace::apply(const Selection &Inputs) { + auto &Ctx = Inputs.AST.getASTContext(); + // First, collect *all* using namespace directives that redeclare the same + // namespace. + std::vector AllDirectives; + FindSameUsings(*TargetDirective, AllDirectives).TraverseAST(Ctx); + // Collect all references to symbols from the namespace for which we're + // removing the directive. + std::vector IdentsToQualify; + FindIdentsToQualify(Ctx, *TargetDirective->getNominatedNamespace(), + IdentsToQualify) + .TraverseAST(Ctx); + // 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 R; +} + +std::string RemoveUsingNamespace::title() const { + return llvm::formatv("Remove using namespace, add qualifiers instead"); +} +} // namespace clangd +} // namespace clang