clang-tools  14.0.0git
RemoveUsingNamespace.cpp
Go to the documentation of this file.
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 "SourceCode.h"
12 #include "refactor/Tweak.h"
13 #include "support/Logger.h"
14 #include "clang/AST/Decl.h"
15 #include "clang/AST/DeclBase.h"
16 #include "clang/AST/DeclCXX.h"
17 #include "clang/AST/RecursiveASTVisitor.h"
18 #include "clang/Basic/SourceLocation.h"
19 #include "clang/Tooling/Core/Replacement.h"
20 #include "clang/Tooling/Refactoring/RecursiveSymbolVisitor.h"
21 #include "llvm/ADT/ScopeExit.h"
22 
23 namespace clang {
24 namespace clangd {
25 namespace {
26 /// Removes the 'using namespace' under the cursor and qualifies all accesses in
27 /// the current file. E.g.,
28 /// using namespace std;
29 /// vector<int> foo(std::map<int, int>);
30 /// Would become:
31 /// std::vector<int> foo(std::map<int, int>);
32 /// Currently limited to using namespace directives inside global namespace to
33 /// simplify implementation. Also the namespace must not contain using
34 /// directives.
35 class RemoveUsingNamespace : public Tweak {
36 public:
37  const char *id() const override;
38 
39  bool prepare(const Selection &Inputs) override;
40  Expected<Effect> apply(const Selection &Inputs) override;
41  std::string title() const override {
42  return "Remove using namespace, re-qualify names instead";
43  }
44  llvm::StringLiteral kind() const override {
46  }
47 
48 private:
49  const UsingDirectiveDecl *TargetDirective = nullptr;
50 };
51 REGISTER_TWEAK(RemoveUsingNamespace)
52 
53 class FindSameUsings : public RecursiveASTVisitor<FindSameUsings> {
54 public:
55  FindSameUsings(const UsingDirectiveDecl &Target,
56  std::vector<const UsingDirectiveDecl *> &Results)
57  : TargetNS(Target.getNominatedNamespace()),
58  TargetCtx(Target.getDeclContext()), Results(Results) {}
59 
60  bool VisitUsingDirectiveDecl(UsingDirectiveDecl *D) {
61  if (D->getNominatedNamespace() != TargetNS ||
62  D->getDeclContext() != TargetCtx)
63  return true;
64  Results.push_back(D);
65  return true;
66  }
67 
68 private:
69  const NamespaceDecl *TargetNS;
70  const DeclContext *TargetCtx;
71  std::vector<const UsingDirectiveDecl *> &Results;
72 };
73 
74 /// Produce edit removing 'using namespace xxx::yyy' and the trailing semicolon.
75 llvm::Expected<tooling::Replacement>
76 removeUsingDirective(ASTContext &Ctx, const UsingDirectiveDecl *D) {
77  auto &SM = Ctx.getSourceManager();
78  llvm::Optional<Token> NextTok =
79  Lexer::findNextToken(D->getEndLoc(), SM, Ctx.getLangOpts());
80  if (!NextTok || NextTok->isNot(tok::semi))
81  return error("no semicolon after using-directive");
82  // FIXME: removing the semicolon may be invalid in some obscure cases, e.g.
83  // if (x) using namespace std; else using namespace bar;
84  return tooling::Replacement(
85  SM,
86  CharSourceRange::getTokenRange(D->getBeginLoc(), NextTok->getLocation()),
87  "", Ctx.getLangOpts());
88 }
89 
90 // Returns true iff the parent of the Node is a TUDecl.
91 bool isTopLevelDecl(const SelectionTree::Node *Node) {
92  return Node->Parent && Node->Parent->ASTNode.get<TranslationUnitDecl>();
93 }
94 
95 // Returns the first visible context that contains this DeclContext.
96 // For example: Returns ns1 for S1 and a.
97 // namespace ns1 {
98 // inline namespace ns2 { struct S1 {}; }
99 // enum E { a, b, c, d };
100 // }
101 const DeclContext *visibleContext(const DeclContext *D) {
102  while (D->isInlineNamespace() || D->isTransparentContext())
103  D = D->getParent();
104  return D;
105 }
106 
107 bool RemoveUsingNamespace::prepare(const Selection &Inputs) {
108  // Find the 'using namespace' directive under the cursor.
109  auto *CA = Inputs.ASTSelection.commonAncestor();
110  if (!CA)
111  return false;
112  TargetDirective = CA->ASTNode.get<UsingDirectiveDecl>();
113  if (!TargetDirective)
114  return false;
115  if (!isa<Decl>(TargetDirective->getDeclContext()))
116  return false;
117  // FIXME: Unavailable for namespaces containing using-namespace decl.
118  // It is non-trivial to deal with cases where identifiers come from the inner
119  // namespace. For example map has to be changed to aa::map.
120  // namespace aa {
121  // namespace bb { struct map {}; }
122  // using namespace bb;
123  // }
124  // using namespace a^a;
125  // int main() { map m; }
126  // We need to make this aware of the transitive using-namespace decls.
127  if (!TargetDirective->getNominatedNamespace()->using_directives().empty())
128  return false;
129  return isTopLevelDecl(CA);
130 }
131 
132 Expected<Tweak::Effect> RemoveUsingNamespace::apply(const Selection &Inputs) {
133  auto &Ctx = Inputs.AST->getASTContext();
134  auto &SM = Ctx.getSourceManager();
135  // First, collect *all* using namespace directives that redeclare the same
136  // namespace.
137  std::vector<const UsingDirectiveDecl *> AllDirectives;
138  FindSameUsings(*TargetDirective, AllDirectives).TraverseAST(Ctx);
139 
140  SourceLocation FirstUsingDirectiveLoc;
141  for (auto *D : AllDirectives) {
142  if (FirstUsingDirectiveLoc.isInvalid() ||
143  SM.isBeforeInTranslationUnit(D->getBeginLoc(), FirstUsingDirectiveLoc))
144  FirstUsingDirectiveLoc = D->getBeginLoc();
145  }
146 
147  // Collect all references to symbols from the namespace for which we're
148  // removing the directive.
149  std::vector<SourceLocation> IdentsToQualify;
150  for (auto &D : Inputs.AST->getLocalTopLevelDecls()) {
152  D,
153  [&](ReferenceLoc Ref) {
154  if (Ref.Qualifier)
155  return; // This reference is already qualified.
156 
157  for (auto *T : Ref.Targets) {
158  if (!visibleContext(T->getDeclContext())
159  ->Equals(TargetDirective->getNominatedNamespace()))
160  return;
161  }
162  SourceLocation Loc = Ref.NameLoc;
163  if (Loc.isMacroID()) {
164  // Avoid adding qualifiers before macro expansions, it's probably
165  // incorrect, e.g.
166  // namespace std { int foo(); }
167  // #define FOO 1 + foo()
168  // using namespace foo; // provides matrix
169  // auto x = FOO; // Must not changed to auto x = std::FOO
170  if (!SM.isMacroArgExpansion(Loc))
171  return; // FIXME: report a warning to the users.
172  Loc = SM.getFileLoc(Ref.NameLoc);
173  }
174  assert(Loc.isFileID());
175  if (SM.getFileID(Loc) != SM.getMainFileID())
176  return; // FIXME: report these to the user as warnings?
177  if (SM.isBeforeInTranslationUnit(Loc, FirstUsingDirectiveLoc))
178  return; // Directive was not visible before this point.
179  IdentsToQualify.push_back(Loc);
180  },
181  Inputs.AST->getHeuristicResolver());
182  }
183  // Remove duplicates.
184  llvm::sort(IdentsToQualify);
185  IdentsToQualify.erase(
186  std::unique(IdentsToQualify.begin(), IdentsToQualify.end()),
187  IdentsToQualify.end());
188 
189  // Produce replacements to remove the using directives.
190  tooling::Replacements R;
191  for (auto *D : AllDirectives) {
192  auto RemoveUsing = removeUsingDirective(Ctx, D);
193  if (!RemoveUsing)
194  return RemoveUsing.takeError();
195  if (auto Err = R.add(*RemoveUsing))
196  return std::move(Err);
197  }
198  // Produce replacements to add the qualifiers.
199  std::string Qualifier = printUsingNamespaceName(Ctx, *TargetDirective) + "::";
200  for (auto Loc : IdentsToQualify) {
201  if (auto Err = R.add(tooling::Replacement(Ctx.getSourceManager(), Loc,
202  /*Length=*/0, Qualifier)))
203  return std::move(Err);
204  }
205  return Effect::mainFileEdit(SM, std::move(R));
206 }
207 
208 } // namespace
209 } // namespace clangd
210 } // namespace clang
Loc
SourceLocation Loc
Definition: KernelNameRestrictionCheck.cpp:45
RecursiveASTVisitor
Selection.h
clang::clangd::error
llvm::Error error(std::error_code EC, const char *Fmt, Ts &&... Vals)
Definition: Logger.h:80
Ctx
Context Ctx
Definition: TUScheduler.cpp:454
FindTarget.h
Target
std::string Target
Definition: QueryDriverDatabase.cpp:64
Inputs
ParseInputs Inputs
Definition: TUScheduler.cpp:451
ns1::ns2::D
@ D
Definition: CategoricalFeature.h:3
Tweak.h
Logger.h
if
if(CLANGD_ENABLE_REMOTE) generate_protos(RemoteIndexProto "Index.proto") generate_protos(MonitoringServiceProto "MonitoringService.proto" GRPC) generate_protos(RemoteIndexServiceProto "Service.proto" DEPENDS "Index.proto" GRPC) target_link_libraries(RemoteIndexServiceProto PRIVATE RemoteIndexProto MonitoringServiceProto) include_directories($
Definition: clangd/index/remote/CMakeLists.txt:1
Results
std::vector< CodeCompletionResult > Results
Definition: CodeComplete.cpp:771
SourceCode.h
clang
===– Representation.cpp - ClangDoc Representation --------—*- C++ -*-===//
Definition: ApplyReplacements.h:27
clang::clangd::CodeAction::REFACTOR_KIND
const static llvm::StringLiteral REFACTOR_KIND
Definition: Protocol.h:980
SM
const SourceManager & SM
Definition: IncludeCleaner.cpp:108
clang::clangd::findExplicitReferences
void findExplicitReferences(const Stmt *S, llvm::function_ref< void(ReferenceLoc)> Out, const HeuristicResolver *Resolver)
Recursively traverse S and report all references explicitly written in the code.
Definition: FindTarget.cpp:1115
clang::clangd::printUsingNamespaceName
std::string printUsingNamespaceName(const ASTContext &Ctx, const UsingDirectiveDecl &D)
Returns the name of the namespace inside the 'using namespace' directive, as written in the code.
Definition: AST.cpp:201
REGISTER_TWEAK
#define REGISTER_TWEAK(Subclass)
Definition: Tweak.h:132
AST.h