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