1 //===--- RemoveUsingNamespace.cpp --------------------------------*- C++-*-===//
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
7 //===----------------------------------------------------------------------===//
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"
23 /// Removes the 'using namespace' under the cursor and qualifies all accesses in
24 /// the current file. E.g.,
25 /// using namespace std;
26 /// vector<int> foo(std::map<int, int>);
28 /// std::vector<int> foo(std::map<int, int>);
29 /// Currently limited to using namespace directives inside global namespace to
30 /// simplify implementation. Also the namespace must not contain using
32 class RemoveUsingNamespace
: public Tweak
{
34 const char *id() const override
;
36 bool prepare(const Selection
&Inputs
) override
;
37 Expected
<Effect
> apply(const Selection
&Inputs
) override
;
38 std::string
title() const override
{
39 return "Remove using namespace, re-qualify names instead";
41 llvm::StringLiteral
kind() const override
{
42 return CodeAction::REFACTOR_KIND
;
46 const UsingDirectiveDecl
*TargetDirective
= nullptr;
48 REGISTER_TWEAK(RemoveUsingNamespace
)
50 class FindSameUsings
: public RecursiveASTVisitor
<FindSameUsings
> {
52 FindSameUsings(const UsingDirectiveDecl
&Target
,
53 std::vector
<const UsingDirectiveDecl
*> &Results
)
54 : TargetNS(Target
.getNominatedNamespace()),
55 TargetCtx(Target
.getDeclContext()), Results(Results
) {}
57 bool VisitUsingDirectiveDecl(UsingDirectiveDecl
*D
) {
58 if (D
->getNominatedNamespace() != TargetNS
||
59 D
->getDeclContext() != TargetCtx
)
66 const NamespaceDecl
*TargetNS
;
67 const DeclContext
*TargetCtx
;
68 std::vector
<const UsingDirectiveDecl
*> &Results
;
71 /// Produce edit removing 'using namespace xxx::yyy' and the trailing semicolon.
72 llvm::Expected
<tooling::Replacement
>
73 removeUsingDirective(ASTContext
&Ctx
, const UsingDirectiveDecl
*D
) {
74 auto &SM
= Ctx
.getSourceManager();
75 llvm::Optional
<Token
> NextTok
=
76 Lexer::findNextToken(D
->getEndLoc(), SM
, Ctx
.getLangOpts());
77 if (!NextTok
|| NextTok
->isNot(tok::semi
))
78 return error("no semicolon after using-directive");
79 // FIXME: removing the semicolon may be invalid in some obscure cases, e.g.
80 // if (x) using namespace std; else using namespace bar;
81 return tooling::Replacement(
83 CharSourceRange::getTokenRange(D
->getBeginLoc(), NextTok
->getLocation()),
84 "", Ctx
.getLangOpts());
87 // Returns true iff the parent of the Node is a TUDecl.
88 bool isTopLevelDecl(const SelectionTree::Node
*Node
) {
89 return Node
->Parent
&& Node
->Parent
->ASTNode
.get
<TranslationUnitDecl
>();
92 // Returns the first visible context that contains this DeclContext.
93 // For example: Returns ns1 for S1 and a.
95 // inline namespace ns2 { struct S1 {}; }
96 // enum E { a, b, c, d };
98 const DeclContext
*visibleContext(const DeclContext
*D
) {
99 while (D
->isInlineNamespace() || D
->isTransparentContext())
104 bool RemoveUsingNamespace::prepare(const Selection
&Inputs
) {
105 // Find the 'using namespace' directive under the cursor.
106 auto *CA
= Inputs
.ASTSelection
.commonAncestor();
109 TargetDirective
= CA
->ASTNode
.get
<UsingDirectiveDecl
>();
110 if (!TargetDirective
)
112 if (!isa
<Decl
>(TargetDirective
->getDeclContext()))
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.
118 // namespace bb { struct map {}; }
119 // using namespace bb;
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())
126 return isTopLevelDecl(CA
);
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
134 std::vector
<const UsingDirectiveDecl
*> AllDirectives
;
135 FindSameUsings(*TargetDirective
, AllDirectives
).TraverseAST(Ctx
);
137 SourceLocation FirstUsingDirectiveLoc
;
138 for (auto *D
: AllDirectives
) {
139 if (FirstUsingDirectiveLoc
.isInvalid() ||
140 SM
.isBeforeInTranslationUnit(D
->getBeginLoc(), FirstUsingDirectiveLoc
))
141 FirstUsingDirectiveLoc
= D
->getBeginLoc();
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(
150 [&](ReferenceLoc Ref
) {
152 return; // This reference is already qualified.
154 for (auto *T
: Ref
.Targets
) {
155 if (!visibleContext(T
->getDeclContext())
156 ->Equals(TargetDirective
->getNominatedNamespace()))
159 SourceLocation Loc
= Ref
.NameLoc
;
160 if (Loc
.isMacroID()) {
161 // Avoid adding qualifiers before macro expansions, it's probably
163 // namespace std { int foo(); }
164 // #define FOO 1 + foo()
165 // using namespace foo; // provides matrix
166 // auto x = FOO; // Must not changed to auto x = std::FOO
167 if (!SM
.isMacroArgExpansion(Loc
))
168 return; // FIXME: report a warning to the users.
169 Loc
= SM
.getFileLoc(Ref
.NameLoc
);
171 assert(Loc
.isFileID());
172 if (SM
.getFileID(Loc
) != SM
.getMainFileID())
173 return; // FIXME: report these to the user as warnings?
174 if (SM
.isBeforeInTranslationUnit(Loc
, FirstUsingDirectiveLoc
))
175 return; // Directive was not visible before this point.
176 IdentsToQualify
.push_back(Loc
);
178 Inputs
.AST
->getHeuristicResolver());
180 // Remove duplicates.
181 llvm::sort(IdentsToQualify
);
182 IdentsToQualify
.erase(
183 std::unique(IdentsToQualify
.begin(), IdentsToQualify
.end()),
184 IdentsToQualify
.end());
186 // Produce replacements to remove the using directives.
187 tooling::Replacements R
;
188 for (auto *D
: AllDirectives
) {
189 auto RemoveUsing
= removeUsingDirective(Ctx
, D
);
191 return RemoveUsing
.takeError();
192 if (auto Err
= R
.add(*RemoveUsing
))
193 return std::move(Err
);
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
);
202 return Effect::mainFileEdit(SM
, std::move(R
));
206 } // namespace clangd