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
21namespace clang {
22namespace clangd {
23namespace {
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.
33class RemoveUsingNamespace : public Tweak {
34public:
35 const char *id() const override;
36
37 bool prepare(const Selection &Inputs) override;
38 Expected<Effect> apply(const Selection &Inputs) override;
39 std::string title() const override {
40 return "Remove using namespace, re-qualify names instead";
41 }
42 llvm::StringLiteral kind() const override {
43 return CodeAction::REFACTOR_KIND;
44 }
45
46private:
47 const UsingDirectiveDecl *TargetDirective = nullptr;
48};
49REGISTER_TWEAK(RemoveUsingNamespace)
50
51class FindSameUsings : public RecursiveASTVisitor<FindSameUsings> {
52public:
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(x: D);
63 return true;
64 }
65
66private:
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.
73llvm::Expected<tooling::Replacement>
74removeUsingDirective(ASTContext &Ctx, const UsingDirectiveDecl *D) {
75 auto &SM = Ctx.getSourceManager();
76 std::optional<Token> NextTok =
77 Lexer::findNextToken(Loc: D->getEndLoc(), SM, LangOpts: Ctx.getLangOpts());
78 if (!NextTok || NextTok->isNot(K: tok::semi))
79 return error(Fmt: "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.
89bool 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`
94bool 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
104bool 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(Node: CA);
127}
128
129Expected<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(AST&: Ctx);
136
137 SourceLocation FirstUsingDirectiveLoc;
138 for (auto *D : AllDirectives) {
139 if (FirstUsingDirectiveLoc.isInvalid() ||
140 SM.isBeforeInTranslationUnit(LHS: D->getBeginLoc(), RHS: 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 Out: [&](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(Loc: Ref.NameLoc);
187 }
188 assert(Loc.isFileID());
189 if (SM.getFileID(SpellingLoc: Loc) != SM.getMainFileID())
190 return; // FIXME: report these to the user as warnings?
191 if (SM.isBeforeInTranslationUnit(LHS: Loc, RHS: FirstUsingDirectiveLoc))
192 return; // Directive was not visible before this point.
193 IdentsToQualify.push_back(x: Loc);
194 },
195 Resolver: Inputs.AST->getHeuristicResolver());
196 }
197 // Remove duplicates.
198 llvm::sort(C&: IdentsToQualify);
199 IdentsToQualify.erase(
200 first: std::unique(first: IdentsToQualify.begin(), last: IdentsToQualify.end()),
201 last: 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(R: *RemoveUsing))
210 return std::move(Err);
211 }
212 // Produce replacements to add the qualifiers.
213 std::string Qualifier = printUsingNamespaceName(Ctx, D: *TargetDirective) + "::";
214 for (auto Loc : IdentsToQualify) {
215 if (auto Err = R.add(R: tooling::Replacement(Ctx.getSourceManager(), Loc,
216 /*Length=*/0, Qualifier)))
217 return std::move(Err);
218 }
219 return Effect::mainFileEdit(SM, Replacements: std::move(R));
220}
221
222} // namespace
223} // namespace clangd
224} // namespace clang
225

source code of clang-tools-extra/clangd/refactor/tweaks/RemoveUsingNamespace.cpp