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