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