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