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