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( 152 D, 153 [&](ReferenceLoc Ref) { 154 if (Ref.Qualifier) 155 return; // This reference is already qualified. 156 157 for (auto *T : Ref.Targets) { 158 if (!visibleContext(T->getDeclContext()) 159 ->Equals(TargetDirective->getNominatedNamespace())) 160 return; 161 } 162 SourceLocation Loc = Ref.NameLoc; 163 if (Loc.isMacroID()) { 164 // Avoid adding qualifiers before macro expansions, it's probably 165 // incorrect, e.g. 166 // namespace std { int foo(); } 167 // #define FOO 1 + foo() 168 // using namespace foo; // provides matrix 169 // auto x = FOO; // Must not changed to auto x = std::FOO 170 if (!SM.isMacroArgExpansion(Loc)) 171 return; // FIXME: report a warning to the users. 172 Loc = SM.getFileLoc(Ref.NameLoc); 173 } 174 assert(Loc.isFileID()); 175 if (SM.getFileID(Loc) != SM.getMainFileID()) 176 return; // FIXME: report these to the user as warnings? 177 if (SM.isBeforeInTranslationUnit(Loc, FirstUsingDirectiveLoc)) 178 return; // Directive was not visible before this point. 179 IdentsToQualify.push_back(Loc); 180 }, 181 Inputs.AST->getHeuristicResolver()); 182 } 183 // Remove duplicates. 184 llvm::sort(IdentsToQualify); 185 IdentsToQualify.erase( 186 std::unique(IdentsToQualify.begin(), IdentsToQualify.end()), 187 IdentsToQualify.end()); 188 189 // Produce replacements to remove the using directives. 190 tooling::Replacements R; 191 for (auto *D : AllDirectives) { 192 auto RemoveUsing = removeUsingDirective(Ctx, D); 193 if (!RemoveUsing) 194 return RemoveUsing.takeError(); 195 if (auto Err = R.add(*RemoveUsing)) 196 return std::move(Err); 197 } 198 // Produce replacements to add the qualifiers. 199 std::string Qualifier = printUsingNamespaceName(Ctx, *TargetDirective) + "::"; 200 for (auto Loc : IdentsToQualify) { 201 if (auto Err = R.add(tooling::Replacement(Ctx.getSourceManager(), Loc, 202 /*Length=*/0, Qualifier))) 203 return std::move(Err); 204 } 205 return Effect::mainFileEdit(SM, std::move(R)); 206 } 207 208 } // namespace 209 } // namespace clangd 210 } // namespace clang 211