xref: /llvm-project/clang-tools-extra/clangd/refactor/tweaks/RemoveUsingNamespace.cpp (revision f71ffd3b735b4d6ae3c12be1806cdd6205b3b378)
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 #include <optional>
20 
21 namespace clang {
22 namespace clangd {
23 namespace {
24 /// Removes the 'using namespace' under the cursor and qualifies all accesses in
25 /// the current file. E.g.,
26 ///   using namespace std;
27 ///   vector<int> foo(std::map<int, int>);
28 /// Would become:
29 ///   std::vector<int> foo(std::map<int, int>);
30 /// Currently limited to using namespace directives inside global namespace to
31 /// simplify implementation. Also the namespace must not contain using
32 /// directives.
33 class RemoveUsingNamespace : public Tweak {
34 public:
35   const char *id() const override;
36 
37   bool prepare(const Selection &Inputs) override;
38   Expected<Effect> apply(const Selection &Inputs) override;
title() const39   std::string title() const override {
40     return "Remove using namespace, re-qualify names instead";
41   }
kind() const42   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:
FindSameUsings(const UsingDirectiveDecl & Target,std::vector<const UsingDirectiveDecl * > & Results)53   FindSameUsings(const UsingDirectiveDecl &Target,
54                  std::vector<const UsingDirectiveDecl *> &Results)
55       : TargetNS(Target.getNominatedNamespace()),
56         TargetCtx(Target.getDeclContext()), Results(Results) {}
57 
VisitUsingDirectiveDecl(UsingDirectiveDecl * D)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>
removeUsingDirective(ASTContext & Ctx,const UsingDirectiveDecl * D)74 removeUsingDirective(ASTContext &Ctx, const UsingDirectiveDecl *D) {
75   auto &SM = Ctx.getSourceManager();
76   std::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.
isTopLevelDecl(const SelectionTree::Node * Node)89 bool isTopLevelDecl(const SelectionTree::Node *Node) {
90   return Node->Parent && Node->Parent->ASTNode.get<TranslationUnitDecl>();
91 }
92 
93 // Return true if `LHS` is declared in `RHS`
isDeclaredIn(const NamedDecl * LHS,const DeclContext * RHS)94 bool isDeclaredIn(const NamedDecl *LHS, const DeclContext *RHS) {
95   const auto *D = LHS->getDeclContext();
96   while (D->isInlineNamespace() || D->isTransparentContext()) {
97     if (D->Equals(RHS))
98       return true;
99     D = D->getParent();
100   }
101   return D->Equals(RHS);
102 }
103 
prepare(const Selection & Inputs)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 
apply(const Selection & Inputs)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 (!isDeclaredIn(T, TargetDirective->getNominatedNamespace()))
156               return;
157             auto Kind = T->getDeclName().getNameKind();
158             // Avoid adding qualifiers before operators, e.g.
159             //   using namespace std;
160             //   cout << "foo"; // Must not changed to std::cout std:: << "foo"
161             if (Kind == DeclarationName::CXXOperatorName)
162               return;
163             // Avoid adding qualifiers before user-defined literals, e.g.
164             //   using namespace std;
165             //   auto s = "foo"s; // Must not changed to auto s = "foo" std::s;
166             // FIXME: Add a using-directive for user-defined literals
167             // declared in an inline namespace, e.g.
168             //   using namespace s^td;
169             //   int main() { cout << "foo"s; }
170             // change to
171             //   using namespace std::literals;
172             //   int main() { std::cout << "foo"s; }
173             if (Kind == DeclarationName::NameKind::CXXLiteralOperatorName)
174               return;
175           }
176           SourceLocation Loc = Ref.NameLoc;
177           if (Loc.isMacroID()) {
178             // Avoid adding qualifiers before macro expansions, it's probably
179             // incorrect, e.g.
180             //   namespace std { int foo(); }
181             //   #define FOO 1 + foo()
182             //   using namespace foo; // provides matrix
183             //   auto x = FOO; // Must not changed to auto x = std::FOO
184             if (!SM.isMacroArgExpansion(Loc))
185               return; // FIXME: report a warning to the users.
186             Loc = SM.getFileLoc(Ref.NameLoc);
187           }
188           assert(Loc.isFileID());
189           if (SM.getFileID(Loc) != SM.getMainFileID())
190             return; // FIXME: report these to the user as warnings?
191           if (SM.isBeforeInTranslationUnit(Loc, FirstUsingDirectiveLoc))
192             return; // Directive was not visible before this point.
193           IdentsToQualify.push_back(Loc);
194         },
195         Inputs.AST->getHeuristicResolver());
196   }
197   // Remove duplicates.
198   llvm::sort(IdentsToQualify);
199   IdentsToQualify.erase(
200       std::unique(IdentsToQualify.begin(), IdentsToQualify.end()),
201       IdentsToQualify.end());
202 
203   // Produce replacements to remove the using directives.
204   tooling::Replacements R;
205   for (auto *D : AllDirectives) {
206     auto RemoveUsing = removeUsingDirective(Ctx, D);
207     if (!RemoveUsing)
208       return RemoveUsing.takeError();
209     if (auto Err = R.add(*RemoveUsing))
210       return std::move(Err);
211   }
212   // Produce replacements to add the qualifiers.
213   std::string Qualifier = printUsingNamespaceName(Ctx, *TargetDirective) + "::";
214   for (auto Loc : IdentsToQualify) {
215     if (auto Err = R.add(tooling::Replacement(Ctx.getSourceManager(), Loc,
216                                               /*Length=*/0, Qualifier)))
217       return std::move(Err);
218   }
219   return Effect::mainFileEdit(SM, std::move(R));
220 }
221 
222 } // namespace
223 } // namespace clangd
224 } // namespace clang
225