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