| //===--- 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 |