//===--- 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 "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 "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); /// 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 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 { public: FindSameUsings(const 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: const NamespaceDecl *TargetNS; const DeclContext *TargetCtx; std::vector &Results; }; /// Produce edit removing 'using namespace xxx::yyy' and the trailing semicolon. llvm::Expected removeUsingDirective(ASTContext &Ctx, const UsingDirectiveDecl *D) { auto &SM = Ctx.getSourceManager(); llvm::Optional NextTok = Lexer::findNextToken(D->getEndLoc(), SM, Ctx.getLangOpts()); if (!NextTok || NextTok->isNot(tok::semi)) return error("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(); } // 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(); if (!TargetDirective) return false; if (!dyn_cast(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 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 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 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)); } } // namespace } // namespace clangd } // namespace clang