xref: /llvm-project/clang-tools-extra/clangd/refactor/tweaks/RemoveUsingNamespace.cpp (revision f71ffd3b735b4d6ae3c12be1806cdd6205b3b378)
1b62b4541SUtkarsh Saxena //===--- RemoveUsingNamespace.cpp --------------------------------*- C++-*-===//
2b62b4541SUtkarsh Saxena //
3b62b4541SUtkarsh Saxena // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4b62b4541SUtkarsh Saxena // See https://llvm.org/LICENSE.txt for license information.
5b62b4541SUtkarsh Saxena // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6b62b4541SUtkarsh Saxena //
7b62b4541SUtkarsh Saxena //===----------------------------------------------------------------------===//
8b62b4541SUtkarsh Saxena #include "AST.h"
9b62b4541SUtkarsh Saxena #include "FindTarget.h"
10b62b4541SUtkarsh Saxena #include "Selection.h"
11b62b4541SUtkarsh Saxena #include "refactor/Tweak.h"
12687e1d71SSam McCall #include "support/Logger.h"
13b62b4541SUtkarsh Saxena #include "clang/AST/Decl.h"
14b62b4541SUtkarsh Saxena #include "clang/AST/DeclBase.h"
15b62b4541SUtkarsh Saxena #include "clang/AST/DeclCXX.h"
16b62b4541SUtkarsh Saxena #include "clang/AST/RecursiveASTVisitor.h"
17b62b4541SUtkarsh Saxena #include "clang/Basic/SourceLocation.h"
18b62b4541SUtkarsh Saxena #include "clang/Tooling/Core/Replacement.h"
1971f55735SKazu Hirata #include <optional>
20b62b4541SUtkarsh Saxena 
21b62b4541SUtkarsh Saxena namespace clang {
22b62b4541SUtkarsh Saxena namespace clangd {
23b62b4541SUtkarsh Saxena namespace {
24b62b4541SUtkarsh Saxena /// Removes the 'using namespace' under the cursor and qualifies all accesses in
25b62b4541SUtkarsh Saxena /// the current file. E.g.,
26b62b4541SUtkarsh Saxena ///   using namespace std;
27b62b4541SUtkarsh Saxena ///   vector<int> foo(std::map<int, int>);
28b62b4541SUtkarsh Saxena /// Would become:
29b62b4541SUtkarsh Saxena ///   std::vector<int> foo(std::map<int, int>);
30b62b4541SUtkarsh Saxena /// Currently limited to using namespace directives inside global namespace to
31b62b4541SUtkarsh Saxena /// simplify implementation. Also the namespace must not contain using
32b62b4541SUtkarsh Saxena /// directives.
33b62b4541SUtkarsh Saxena class RemoveUsingNamespace : public Tweak {
34b62b4541SUtkarsh Saxena public:
35b62b4541SUtkarsh Saxena   const char *id() const override;
36b62b4541SUtkarsh Saxena 
37b62b4541SUtkarsh Saxena   bool prepare(const Selection &Inputs) override;
38b62b4541SUtkarsh Saxena   Expected<Effect> apply(const Selection &Inputs) override;
title() const39aad3ea89SHaojian Wu   std::string title() const override {
40aad3ea89SHaojian Wu     return "Remove using namespace, re-qualify names instead";
41aad3ea89SHaojian Wu   }
kind() const4217747d2eSSam McCall   llvm::StringLiteral kind() const override {
4317747d2eSSam McCall     return CodeAction::REFACTOR_KIND;
4417747d2eSSam McCall   }
45b62b4541SUtkarsh Saxena 
46b62b4541SUtkarsh Saxena private:
47b62b4541SUtkarsh Saxena   const UsingDirectiveDecl *TargetDirective = nullptr;
48b62b4541SUtkarsh Saxena };
49b62b4541SUtkarsh Saxena REGISTER_TWEAK(RemoveUsingNamespace)
50b62b4541SUtkarsh Saxena 
51b62b4541SUtkarsh Saxena class FindSameUsings : public RecursiveASTVisitor<FindSameUsings> {
52b62b4541SUtkarsh Saxena public:
FindSameUsings(const UsingDirectiveDecl & Target,std::vector<const UsingDirectiveDecl * > & Results)53b62b4541SUtkarsh Saxena   FindSameUsings(const UsingDirectiveDecl &Target,
54b62b4541SUtkarsh Saxena                  std::vector<const UsingDirectiveDecl *> &Results)
55b62b4541SUtkarsh Saxena       : TargetNS(Target.getNominatedNamespace()),
56b62b4541SUtkarsh Saxena         TargetCtx(Target.getDeclContext()), Results(Results) {}
57b62b4541SUtkarsh Saxena 
VisitUsingDirectiveDecl(UsingDirectiveDecl * D)58b62b4541SUtkarsh Saxena   bool VisitUsingDirectiveDecl(UsingDirectiveDecl *D) {
59b62b4541SUtkarsh Saxena     if (D->getNominatedNamespace() != TargetNS ||
60b62b4541SUtkarsh Saxena         D->getDeclContext() != TargetCtx)
61b62b4541SUtkarsh Saxena       return true;
62b62b4541SUtkarsh Saxena     Results.push_back(D);
63b62b4541SUtkarsh Saxena     return true;
64b62b4541SUtkarsh Saxena   }
65b62b4541SUtkarsh Saxena 
66b62b4541SUtkarsh Saxena private:
67b62b4541SUtkarsh Saxena   const NamespaceDecl *TargetNS;
68b62b4541SUtkarsh Saxena   const DeclContext *TargetCtx;
69b62b4541SUtkarsh Saxena   std::vector<const UsingDirectiveDecl *> &Results;
70b62b4541SUtkarsh Saxena };
71b62b4541SUtkarsh Saxena 
72b62b4541SUtkarsh Saxena /// Produce edit removing 'using namespace xxx::yyy' and the trailing semicolon.
73b62b4541SUtkarsh Saxena llvm::Expected<tooling::Replacement>
removeUsingDirective(ASTContext & Ctx,const UsingDirectiveDecl * D)74b62b4541SUtkarsh Saxena removeUsingDirective(ASTContext &Ctx, const UsingDirectiveDecl *D) {
75b62b4541SUtkarsh Saxena   auto &SM = Ctx.getSourceManager();
76*f71ffd3bSKazu Hirata   std::optional<Token> NextTok =
77b62b4541SUtkarsh Saxena       Lexer::findNextToken(D->getEndLoc(), SM, Ctx.getLangOpts());
78b62b4541SUtkarsh Saxena   if (!NextTok || NextTok->isNot(tok::semi))
79687e1d71SSam McCall     return error("no semicolon after using-directive");
80b62b4541SUtkarsh Saxena   // FIXME: removing the semicolon may be invalid in some obscure cases, e.g.
81b62b4541SUtkarsh Saxena   //        if (x) using namespace std; else using namespace bar;
82b62b4541SUtkarsh Saxena   return tooling::Replacement(
83b62b4541SUtkarsh Saxena       SM,
84b62b4541SUtkarsh Saxena       CharSourceRange::getTokenRange(D->getBeginLoc(), NextTok->getLocation()),
85b62b4541SUtkarsh Saxena       "", Ctx.getLangOpts());
86b62b4541SUtkarsh Saxena }
87b62b4541SUtkarsh Saxena 
88b62b4541SUtkarsh Saxena // Returns true iff the parent of the Node is a TUDecl.
isTopLevelDecl(const SelectionTree::Node * Node)89b62b4541SUtkarsh Saxena bool isTopLevelDecl(const SelectionTree::Node *Node) {
90b62b4541SUtkarsh Saxena   return Node->Parent && Node->Parent->ASTNode.get<TranslationUnitDecl>();
91b62b4541SUtkarsh Saxena }
92b62b4541SUtkarsh Saxena 
9346575f60Sv1nh1shungry // Return true if `LHS` is declared in `RHS`
isDeclaredIn(const NamedDecl * LHS,const DeclContext * RHS)9446575f60Sv1nh1shungry bool isDeclaredIn(const NamedDecl *LHS, const DeclContext *RHS) {
9546575f60Sv1nh1shungry   const auto *D = LHS->getDeclContext();
9646575f60Sv1nh1shungry   while (D->isInlineNamespace() || D->isTransparentContext()) {
9746575f60Sv1nh1shungry     if (D->Equals(RHS))
9846575f60Sv1nh1shungry       return true;
99b62b4541SUtkarsh Saxena     D = D->getParent();
10046575f60Sv1nh1shungry   }
10146575f60Sv1nh1shungry   return D->Equals(RHS);
102b62b4541SUtkarsh Saxena }
103b62b4541SUtkarsh Saxena 
prepare(const Selection & Inputs)104b62b4541SUtkarsh Saxena bool RemoveUsingNamespace::prepare(const Selection &Inputs) {
105b62b4541SUtkarsh Saxena   // Find the 'using namespace' directive under the cursor.
106b62b4541SUtkarsh Saxena   auto *CA = Inputs.ASTSelection.commonAncestor();
107b62b4541SUtkarsh Saxena   if (!CA)
108b62b4541SUtkarsh Saxena     return false;
109b62b4541SUtkarsh Saxena   TargetDirective = CA->ASTNode.get<UsingDirectiveDecl>();
110b62b4541SUtkarsh Saxena   if (!TargetDirective)
111b62b4541SUtkarsh Saxena     return false;
1127af6a134SNathan James   if (!isa<Decl>(TargetDirective->getDeclContext()))
113b62b4541SUtkarsh Saxena     return false;
114b62b4541SUtkarsh Saxena   // FIXME: Unavailable for namespaces containing using-namespace decl.
115b62b4541SUtkarsh Saxena   // It is non-trivial to deal with cases where identifiers come from the inner
116b62b4541SUtkarsh Saxena   // namespace. For example map has to be changed to aa::map.
117b62b4541SUtkarsh Saxena   // namespace aa {
118b62b4541SUtkarsh Saxena   //   namespace bb { struct map {}; }
119b62b4541SUtkarsh Saxena   //   using namespace bb;
120b62b4541SUtkarsh Saxena   // }
121b62b4541SUtkarsh Saxena   // using namespace a^a;
122b62b4541SUtkarsh Saxena   // int main() { map m; }
123b62b4541SUtkarsh Saxena   // We need to make this aware of the transitive using-namespace decls.
124b62b4541SUtkarsh Saxena   if (!TargetDirective->getNominatedNamespace()->using_directives().empty())
125b62b4541SUtkarsh Saxena     return false;
126b62b4541SUtkarsh Saxena   return isTopLevelDecl(CA);
127b62b4541SUtkarsh Saxena }
128b62b4541SUtkarsh Saxena 
apply(const Selection & Inputs)129b62b4541SUtkarsh Saxena Expected<Tweak::Effect> RemoveUsingNamespace::apply(const Selection &Inputs) {
1307dc388bdSSam McCall   auto &Ctx = Inputs.AST->getASTContext();
131b62b4541SUtkarsh Saxena   auto &SM = Ctx.getSourceManager();
132b62b4541SUtkarsh Saxena   // First, collect *all* using namespace directives that redeclare the same
133b62b4541SUtkarsh Saxena   // namespace.
134b62b4541SUtkarsh Saxena   std::vector<const UsingDirectiveDecl *> AllDirectives;
135b62b4541SUtkarsh Saxena   FindSameUsings(*TargetDirective, AllDirectives).TraverseAST(Ctx);
136b62b4541SUtkarsh Saxena 
137b62b4541SUtkarsh Saxena   SourceLocation FirstUsingDirectiveLoc;
138b62b4541SUtkarsh Saxena   for (auto *D : AllDirectives) {
139b62b4541SUtkarsh Saxena     if (FirstUsingDirectiveLoc.isInvalid() ||
140b62b4541SUtkarsh Saxena         SM.isBeforeInTranslationUnit(D->getBeginLoc(), FirstUsingDirectiveLoc))
141b62b4541SUtkarsh Saxena       FirstUsingDirectiveLoc = D->getBeginLoc();
142b62b4541SUtkarsh Saxena   }
143b62b4541SUtkarsh Saxena 
144b62b4541SUtkarsh Saxena   // Collect all references to symbols from the namespace for which we're
145b62b4541SUtkarsh Saxena   // removing the directive.
146b62b4541SUtkarsh Saxena   std::vector<SourceLocation> IdentsToQualify;
1477dc388bdSSam McCall   for (auto &D : Inputs.AST->getLocalTopLevelDecls()) {
1489510b094SNathan Ridge     findExplicitReferences(
1499510b094SNathan Ridge         D,
1509510b094SNathan Ridge         [&](ReferenceLoc Ref) {
151b62b4541SUtkarsh Saxena           if (Ref.Qualifier)
152b62b4541SUtkarsh Saxena             return; // This reference is already qualified.
153b62b4541SUtkarsh Saxena 
154b62b4541SUtkarsh Saxena           for (auto *T : Ref.Targets) {
15546575f60Sv1nh1shungry             if (!isDeclaredIn(T, TargetDirective->getNominatedNamespace()))
156b62b4541SUtkarsh Saxena               return;
15782ca918bSv1nh1shungry             auto Kind = T->getDeclName().getNameKind();
158f5a2ef80Sv1nh1shungry             // Avoid adding qualifiers before operators, e.g.
159f5a2ef80Sv1nh1shungry             //   using namespace std;
160f5a2ef80Sv1nh1shungry             //   cout << "foo"; // Must not changed to std::cout std:: << "foo"
16182ca918bSv1nh1shungry             if (Kind == DeclarationName::CXXOperatorName)
16282ca918bSv1nh1shungry               return;
16382ca918bSv1nh1shungry             // Avoid adding qualifiers before user-defined literals, e.g.
16482ca918bSv1nh1shungry             //   using namespace std;
16582ca918bSv1nh1shungry             //   auto s = "foo"s; // Must not changed to auto s = "foo" std::s;
16682ca918bSv1nh1shungry             // FIXME: Add a using-directive for user-defined literals
16782ca918bSv1nh1shungry             // declared in an inline namespace, e.g.
16882ca918bSv1nh1shungry             //   using namespace s^td;
16982ca918bSv1nh1shungry             //   int main() { cout << "foo"s; }
17082ca918bSv1nh1shungry             // change to
17182ca918bSv1nh1shungry             //   using namespace std::literals;
17282ca918bSv1nh1shungry             //   int main() { std::cout << "foo"s; }
17382ca918bSv1nh1shungry             if (Kind == DeclarationName::NameKind::CXXLiteralOperatorName)
174f5a2ef80Sv1nh1shungry               return;
175b62b4541SUtkarsh Saxena           }
176b62b4541SUtkarsh Saxena           SourceLocation Loc = Ref.NameLoc;
177b62b4541SUtkarsh Saxena           if (Loc.isMacroID()) {
178b62b4541SUtkarsh Saxena             // Avoid adding qualifiers before macro expansions, it's probably
179b62b4541SUtkarsh Saxena             // incorrect, e.g.
180b62b4541SUtkarsh Saxena             //   namespace std { int foo(); }
181b62b4541SUtkarsh Saxena             //   #define FOO 1 + foo()
182b62b4541SUtkarsh Saxena             //   using namespace foo; // provides matrix
183b62b4541SUtkarsh Saxena             //   auto x = FOO; // Must not changed to auto x = std::FOO
184b62b4541SUtkarsh Saxena             if (!SM.isMacroArgExpansion(Loc))
185b62b4541SUtkarsh Saxena               return; // FIXME: report a warning to the users.
186b62b4541SUtkarsh Saxena             Loc = SM.getFileLoc(Ref.NameLoc);
187b62b4541SUtkarsh Saxena           }
188b62b4541SUtkarsh Saxena           assert(Loc.isFileID());
189b62b4541SUtkarsh Saxena           if (SM.getFileID(Loc) != SM.getMainFileID())
190b62b4541SUtkarsh Saxena             return; // FIXME: report these to the user as warnings?
191b62b4541SUtkarsh Saxena           if (SM.isBeforeInTranslationUnit(Loc, FirstUsingDirectiveLoc))
192b62b4541SUtkarsh Saxena             return; // Directive was not visible before this point.
193b62b4541SUtkarsh Saxena           IdentsToQualify.push_back(Loc);
1949510b094SNathan Ridge         },
1959510b094SNathan Ridge         Inputs.AST->getHeuristicResolver());
196b62b4541SUtkarsh Saxena   }
197b62b4541SUtkarsh Saxena   // Remove duplicates.
198b62b4541SUtkarsh Saxena   llvm::sort(IdentsToQualify);
199b62b4541SUtkarsh Saxena   IdentsToQualify.erase(
200b62b4541SUtkarsh Saxena       std::unique(IdentsToQualify.begin(), IdentsToQualify.end()),
201b62b4541SUtkarsh Saxena       IdentsToQualify.end());
202b62b4541SUtkarsh Saxena 
203b62b4541SUtkarsh Saxena   // Produce replacements to remove the using directives.
204b62b4541SUtkarsh Saxena   tooling::Replacements R;
205b62b4541SUtkarsh Saxena   for (auto *D : AllDirectives) {
206b62b4541SUtkarsh Saxena     auto RemoveUsing = removeUsingDirective(Ctx, D);
207b62b4541SUtkarsh Saxena     if (!RemoveUsing)
208b62b4541SUtkarsh Saxena       return RemoveUsing.takeError();
209b62b4541SUtkarsh Saxena     if (auto Err = R.add(*RemoveUsing))
210b62b4541SUtkarsh Saxena       return std::move(Err);
211b62b4541SUtkarsh Saxena   }
212b62b4541SUtkarsh Saxena   // Produce replacements to add the qualifiers.
213b62b4541SUtkarsh Saxena   std::string Qualifier = printUsingNamespaceName(Ctx, *TargetDirective) + "::";
214b62b4541SUtkarsh Saxena   for (auto Loc : IdentsToQualify) {
215b62b4541SUtkarsh Saxena     if (auto Err = R.add(tooling::Replacement(Ctx.getSourceManager(), Loc,
216b62b4541SUtkarsh Saxena                                               /*Length=*/0, Qualifier)))
217b62b4541SUtkarsh Saxena       return std::move(Err);
218b62b4541SUtkarsh Saxena   }
219b62b4541SUtkarsh Saxena   return Effect::mainFileEdit(SM, std::move(R));
220b62b4541SUtkarsh Saxena }
221b62b4541SUtkarsh Saxena 
222b62b4541SUtkarsh Saxena } // namespace
223b62b4541SUtkarsh Saxena } // namespace clangd
224b62b4541SUtkarsh Saxena } // namespace clang
225