1 //===--- RemoveUsingNamespace.cpp --------------------------------*- C++-*-===// 2 // 3 // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. 4 // See https://llvm.org/LICENSE.txt for license information. 5 // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception 6 // 7 //===----------------------------------------------------------------------===// 8 #include "AST.h" 9 #include "FindTarget.h" 10 #include "Selection.h" 11 #include "SourceCode.h" 12 #include "refactor/Tweak.h" 13 #include "support/Logger.h" 14 #include "clang/AST/Decl.h" 15 #include "clang/AST/DeclBase.h" 16 #include "clang/AST/DeclCXX.h" 17 #include "clang/AST/RecursiveASTVisitor.h" 18 #include "clang/Basic/SourceLocation.h" 19 #include "clang/Tooling/Core/Replacement.h" 20 #include "clang/Tooling/Refactoring/RecursiveSymbolVisitor.h" 21 #include "llvm/ADT/ScopeExit.h" 22 23 namespace clang { 24 namespace clangd { 25 namespace { 26 /// Removes the 'using namespace' under the cursor and qualifies all accesses in 27 /// the current file. E.g., 28 /// using namespace std; 29 /// vector<int> foo(std::map<int, int>); 30 /// Would become: 31 /// std::vector<int> foo(std::map<int, int>); 32 /// Currently limited to using namespace directives inside global namespace to 33 /// simplify implementation. Also the namespace must not contain using 34 /// directives. 35 class RemoveUsingNamespace : public Tweak { 36 public: 37 const char *id() const override; 38 39 bool prepare(const Selection &Inputs) override; 40 Expected<Effect> apply(const Selection &Inputs) override; 41 std::string title() const override { 42 return "Remove using namespace, re-qualify names instead"; 43 } 44 llvm::StringLiteral kind() const override { 45 return CodeAction::REFACTOR_KIND; 46 } 47 48 private: 49 const UsingDirectiveDecl *TargetDirective = nullptr; 50 }; 51 REGISTER_TWEAK(RemoveUsingNamespace) 52 53 class FindSameUsings : public RecursiveASTVisitor<FindSameUsings> { 54 public: 55 FindSameUsings(const UsingDirectiveDecl &Target, 56 std::vector<const UsingDirectiveDecl *> &Results) 57 : TargetNS(Target.getNominatedNamespace()), 58 TargetCtx(Target.getDeclContext()), Results(Results) {} 59 60 bool VisitUsingDirectiveDecl(UsingDirectiveDecl *D) { 61 if (D->getNominatedNamespace() != TargetNS || 62 D->getDeclContext() != TargetCtx) 63 return true; 64 Results.push_back(D); 65 return true; 66 } 67 68 private: 69 const NamespaceDecl *TargetNS; 70 const DeclContext *TargetCtx; 71 std::vector<const UsingDirectiveDecl *> &Results; 72 }; 73 74 /// Produce edit removing 'using namespace xxx::yyy' and the trailing semicolon. 75 llvm::Expected<tooling::Replacement> 76 removeUsingDirective(ASTContext &Ctx, const UsingDirectiveDecl *D) { 77 auto &SM = Ctx.getSourceManager(); 78 llvm::Optional<Token> NextTok = 79 Lexer::findNextToken(D->getEndLoc(), SM, Ctx.getLangOpts()); 80 if (!NextTok || NextTok->isNot(tok::semi)) 81 return error("no semicolon after using-directive"); 82 // FIXME: removing the semicolon may be invalid in some obscure cases, e.g. 83 // if (x) using namespace std; else using namespace bar; 84 return tooling::Replacement( 85 SM, 86 CharSourceRange::getTokenRange(D->getBeginLoc(), NextTok->getLocation()), 87 "", Ctx.getLangOpts()); 88 } 89 90 // Returns true iff the parent of the Node is a TUDecl. 91 bool isTopLevelDecl(const SelectionTree::Node *Node) { 92 return Node->Parent && Node->Parent->ASTNode.get<TranslationUnitDecl>(); 93 } 94 95 // Returns the first visible context that contains this DeclContext. 96 // For example: Returns ns1 for S1 and a. 97 // namespace ns1 { 98 // inline namespace ns2 { struct S1 {}; } 99 // enum E { a, b, c, d }; 100 // } 101 const DeclContext *visibleContext(const DeclContext *D) { 102 while (D->isInlineNamespace() || D->isTransparentContext()) 103 D = D->getParent(); 104 return D; 105 } 106 107 bool RemoveUsingNamespace::prepare(const Selection &Inputs) { 108 // Find the 'using namespace' directive under the cursor. 109 auto *CA = Inputs.ASTSelection.commonAncestor(); 110 if (!CA) 111 return false; 112 TargetDirective = CA->ASTNode.get<UsingDirectiveDecl>(); 113 if (!TargetDirective) 114 return false; 115 if (!isa<Decl>(TargetDirective->getDeclContext())) 116 return false; 117 // FIXME: Unavailable for namespaces containing using-namespace decl. 118 // It is non-trivial to deal with cases where identifiers come from the inner 119 // namespace. For example map has to be changed to aa::map. 120 // namespace aa { 121 // namespace bb { struct map {}; } 122 // using namespace bb; 123 // } 124 // using namespace a^a; 125 // int main() { map m; } 126 // We need to make this aware of the transitive using-namespace decls. 127 if (!TargetDirective->getNominatedNamespace()->using_directives().empty()) 128 return false; 129 return isTopLevelDecl(CA); 130 } 131 132 Expected<Tweak::Effect> RemoveUsingNamespace::apply(const Selection &Inputs) { 133 auto &Ctx = Inputs.AST->getASTContext(); 134 auto &SM = Ctx.getSourceManager(); 135 // First, collect *all* using namespace directives that redeclare the same 136 // namespace. 137 std::vector<const UsingDirectiveDecl *> AllDirectives; 138 FindSameUsings(*TargetDirective, AllDirectives).TraverseAST(Ctx); 139 140 SourceLocation FirstUsingDirectiveLoc; 141 for (auto *D : AllDirectives) { 142 if (FirstUsingDirectiveLoc.isInvalid() || 143 SM.isBeforeInTranslationUnit(D->getBeginLoc(), FirstUsingDirectiveLoc)) 144 FirstUsingDirectiveLoc = D->getBeginLoc(); 145 } 146 147 // Collect all references to symbols from the namespace for which we're 148 // removing the directive. 149 std::vector<SourceLocation> IdentsToQualify; 150 for (auto &D : Inputs.AST->getLocalTopLevelDecls()) { 151 findExplicitReferences(D, [&](ReferenceLoc Ref) { 152 if (Ref.Qualifier) 153 return; // This reference is already qualified. 154 155 for (auto *T : Ref.Targets) { 156 if (!visibleContext(T->getDeclContext()) 157 ->Equals(TargetDirective->getNominatedNamespace())) 158 return; 159 } 160 SourceLocation Loc = Ref.NameLoc; 161 if (Loc.isMacroID()) { 162 // Avoid adding qualifiers before macro expansions, it's probably 163 // incorrect, e.g. 164 // namespace std { int foo(); } 165 // #define FOO 1 + foo() 166 // using namespace foo; // provides matrix 167 // auto x = FOO; // Must not changed to auto x = std::FOO 168 if (!SM.isMacroArgExpansion(Loc)) 169 return; // FIXME: report a warning to the users. 170 Loc = SM.getFileLoc(Ref.NameLoc); 171 } 172 assert(Loc.isFileID()); 173 if (SM.getFileID(Loc) != SM.getMainFileID()) 174 return; // FIXME: report these to the user as warnings? 175 if (SM.isBeforeInTranslationUnit(Loc, FirstUsingDirectiveLoc)) 176 return; // Directive was not visible before this point. 177 IdentsToQualify.push_back(Loc); 178 }); 179 } 180 // Remove duplicates. 181 llvm::sort(IdentsToQualify); 182 IdentsToQualify.erase( 183 std::unique(IdentsToQualify.begin(), IdentsToQualify.end()), 184 IdentsToQualify.end()); 185 186 // Produce replacements to remove the using directives. 187 tooling::Replacements R; 188 for (auto *D : AllDirectives) { 189 auto RemoveUsing = removeUsingDirective(Ctx, D); 190 if (!RemoveUsing) 191 return RemoveUsing.takeError(); 192 if (auto Err = R.add(*RemoveUsing)) 193 return std::move(Err); 194 } 195 // Produce replacements to add the qualifiers. 196 std::string Qualifier = printUsingNamespaceName(Ctx, *TargetDirective) + "::"; 197 for (auto Loc : IdentsToQualify) { 198 if (auto Err = R.add(tooling::Replacement(Ctx.getSourceManager(), Loc, 199 /*Length=*/0, Qualifier))) 200 return std::move(Err); 201 } 202 return Effect::mainFileEdit(SM, std::move(R)); 203 } 204 205 } // namespace 206 } // namespace clangd 207 } // namespace clang 208