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 "SourceCode.h"
12 #include "refactor/Tweak.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 "clang/Tooling/Refactoring/RecursiveSymbolVisitor.h"
20 #include "llvm/ADT/ScopeExit.h"
25 /// Removes the 'using namespace' under the cursor and qualifies all accesses in
26 /// the current file. E.g.,
27 /// using namespace std;
28 /// vector<int> foo(std::map<int, int>);
30 /// std::vector<int> foo(std::map<int, int>);
31 /// Currently limited to using namespace directives inside global namespace to
32 /// simplify implementation. Also the namespace must not contain using
34 class RemoveUsingNamespace : public Tweak {
36 const char *id() const override;
38 bool prepare(const Selection &Inputs) override;
39 Expected<Effect> apply(const Selection &Inputs) override;
40 std::string title() const override;
41 Intent intent() const override { return Refactor; }
44 const UsingDirectiveDecl *TargetDirective = nullptr;
46 REGISTER_TWEAK(RemoveUsingNamespace)
48 class FindSameUsings : public RecursiveASTVisitor<FindSameUsings> {
50 FindSameUsings(const UsingDirectiveDecl &Target,
51 std::vector<const UsingDirectiveDecl *> &Results)
52 : TargetNS(Target.getNominatedNamespace()),
53 TargetCtx(Target.getDeclContext()), Results(Results) {}
55 bool VisitUsingDirectiveDecl(UsingDirectiveDecl *D) {
56 if (D->getNominatedNamespace() != TargetNS ||
57 D->getDeclContext() != TargetCtx)
64 const NamespaceDecl *TargetNS;
65 const DeclContext *TargetCtx;
66 std::vector<const UsingDirectiveDecl *> &Results;
69 /// Produce edit removing 'using namespace xxx::yyy' and the trailing semicolon.
70 llvm::Expected<tooling::Replacement>
71 removeUsingDirective(ASTContext &Ctx, const UsingDirectiveDecl *D) {
72 auto &SM = Ctx.getSourceManager();
73 llvm::Optional<Token> NextTok =
74 Lexer::findNextToken(D->getEndLoc(), SM, Ctx.getLangOpts());
75 if (!NextTok || NextTok->isNot(tok::semi))
76 return llvm::createStringError(llvm::inconvertibleErrorCode(),
77 "no semicolon after using-directive");
78 // FIXME: removing the semicolon may be invalid in some obscure cases, e.g.
79 // if (x) using namespace std; else using namespace bar;
80 return tooling::Replacement(
82 CharSourceRange::getTokenRange(D->getBeginLoc(), NextTok->getLocation()),
83 "", Ctx.getLangOpts());
86 // Returns true iff the parent of the Node is a TUDecl.
87 bool isTopLevelDecl(const SelectionTree::Node *Node) {
88 return Node->Parent && Node->Parent->ASTNode.get<TranslationUnitDecl>();
91 // Returns the first visible context that contains this DeclContext.
92 // For example: Returns ns1 for S1 and a.
94 // inline namespace ns2 { struct S1 {}; }
95 // enum E { a, b, c, d };
97 const DeclContext *visibleContext(const DeclContext *D) {
98 while (D->isInlineNamespace() || D->isTransparentContext())
103 bool RemoveUsingNamespace::prepare(const Selection &Inputs) {
104 // Find the 'using namespace' directive under the cursor.
105 auto *CA = Inputs.ASTSelection.commonAncestor();
108 TargetDirective = CA->ASTNode.get<UsingDirectiveDecl>();
109 if (!TargetDirective)
111 if (!dyn_cast<Decl>(TargetDirective->getDeclContext()))
113 // FIXME: Unavailable for namespaces containing using-namespace decl.
114 // It is non-trivial to deal with cases where identifiers come from the inner
115 // namespace. For example map has to be changed to aa::map.
117 // namespace bb { struct map {}; }
118 // using namespace bb;
120 // using namespace a^a;
121 // int main() { map m; }
122 // We need to make this aware of the transitive using-namespace decls.
123 if (!TargetDirective->getNominatedNamespace()->using_directives().empty())
125 return isTopLevelDecl(CA);
128 Expected<Tweak::Effect> RemoveUsingNamespace::apply(const Selection &Inputs) {
129 auto &Ctx = Inputs.AST.getASTContext();
130 auto &SM = Ctx.getSourceManager();
131 // First, collect *all* using namespace directives that redeclare the same
133 std::vector<const UsingDirectiveDecl *> AllDirectives;
134 FindSameUsings(*TargetDirective, AllDirectives).TraverseAST(Ctx);
136 SourceLocation FirstUsingDirectiveLoc;
137 for (auto *D : AllDirectives) {
138 if (FirstUsingDirectiveLoc.isInvalid() ||
139 SM.isBeforeInTranslationUnit(D->getBeginLoc(), FirstUsingDirectiveLoc))
140 FirstUsingDirectiveLoc = D->getBeginLoc();
143 // Collect all references to symbols from the namespace for which we're
144 // removing the directive.
145 std::vector<SourceLocation> IdentsToQualify;
146 for (auto &D : Inputs.AST.getLocalTopLevelDecls()) {
147 findExplicitReferences(D, [&](ReferenceLoc Ref) {
149 return; // This reference is already qualified.
151 for (auto *T : Ref.Targets) {
152 if (!visibleContext(T->getDeclContext())
153 ->Equals(TargetDirective->getNominatedNamespace()))
156 SourceLocation Loc = Ref.NameLoc;
157 if (Loc.isMacroID()) {
158 // Avoid adding qualifiers before macro expansions, it's probably
160 // namespace std { int foo(); }
161 // #define FOO 1 + foo()
162 // using namespace foo; // provides matrix
163 // auto x = FOO; // Must not changed to auto x = std::FOO
164 if (!SM.isMacroArgExpansion(Loc))
165 return; // FIXME: report a warning to the users.
166 Loc = SM.getFileLoc(Ref.NameLoc);
168 assert(Loc.isFileID());
169 if (SM.getFileID(Loc) != SM.getMainFileID())
170 return; // FIXME: report these to the user as warnings?
171 if (SM.isBeforeInTranslationUnit(Loc, FirstUsingDirectiveLoc))
172 return; // Directive was not visible before this point.
173 IdentsToQualify.push_back(Loc);
176 // Remove duplicates.
177 llvm::sort(IdentsToQualify);
178 IdentsToQualify.erase(
179 std::unique(IdentsToQualify.begin(), IdentsToQualify.end()),
180 IdentsToQualify.end());
182 // Produce replacements to remove the using directives.
183 tooling::Replacements R;
184 for (auto *D : AllDirectives) {
185 auto RemoveUsing = removeUsingDirective(Ctx, D);
187 return RemoveUsing.takeError();
188 if (auto Err = R.add(*RemoveUsing))
189 return std::move(Err);
191 // Produce replacements to add the qualifiers.
192 std::string Qualifier = printUsingNamespaceName(Ctx, *TargetDirective) + "::";
193 for (auto Loc : IdentsToQualify) {
194 if (auto Err = R.add(tooling::Replacement(Ctx.getSourceManager(), Loc,
195 /*Length=*/0, Qualifier)))
196 return std::move(Err);
198 return Effect::mainFileEdit(SM, std::move(R));
201 std::string RemoveUsingNamespace::title() const {
202 return llvm::formatv("Remove using namespace, re-qualify names instead.");
205 } // namespace clangd