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