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 | |
21 | namespace clang { |
22 | namespace clangd { |
23 | namespace { |
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. |
33 | class RemoveUsingNamespace : public Tweak { |
34 | public: |
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 | |
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(x: 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 | 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. |
89 | bool 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` |
94 | bool 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 | |
104 | bool 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 | |
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 |
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 | |