ilya-biryukov created this revision.
ilya-biryukov added a reviewer: sammccall.
Herald added subscribers: kadircet, jfb, arphaman, mgrang, jkorous, MaskRay,
ioeric, mgorny.
Only available in the source files to fit into the model of single-file
actions. Doing the same in headers would require more complicated
machinery, which is out of scope of code actions.
Repository:
rCTE Clang Tools Extra
https://reviews.llvm.org/D56612
Files:
clangd/AST.cpp
clangd/AST.h
clangd/CMakeLists.txt
clangd/CodeActions.cpp
clangd/refactor/actions/RemoveUsingNamespace.cpp
clangd/refactor/actions/RemoveUsingNamespace.h
Index: clangd/refactor/actions/RemoveUsingNamespace.h
===================================================================
--- /dev/null
+++ clangd/refactor/actions/RemoveUsingNamespace.h
@@ -0,0 +1,32 @@
+//===--- RemoveUsingNamespace.h ----------------------------------*- C++-*-===//
+//
+// The LLVM Compiler Infrastructure
+//
+// This file is distributed under the University of Illinois Open Source
+// License. See LICENSE.TXT for details.
+//
+//===----------------------------------------------------------------------===//
+// A code action that removes the 'using namespace' under the cursor and
+// qualifies all accesses in the current file. E.g.,
+// using namespace std;
+// vector<int> foo(std::map<int, int>);
+// Would become:
+// std::vector<int> foo(std::map<int, int>);
+//===----------------------------------------------------------------------===//
+
+#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANGD_REFACTOR_ACTIONS_REMOVEUSINGNAMESPACE_H
+#define LLVM_CLANG_TOOLS_EXTRA_CLANGD_REFACTOR_ACTIONS_REMOVEUSINGNAMESPACE_H
+
+#include "refactor/ActionProvider.h"
+
+namespace clang {
+namespace clangd {
+
+class RemoveUsingNamespace : public ActionProvider {
+ llvm::Optional<PreparedAction> prepare(const ActionInputs &Inputs) override;
+};
+
+} // namespace clangd
+} // namespace clang
+
+#endif
Index: clangd/refactor/actions/RemoveUsingNamespace.cpp
===================================================================
--- /dev/null
+++ clangd/refactor/actions/RemoveUsingNamespace.cpp
@@ -0,0 +1,189 @@
+//===--- RemoveUsingNamespace.cpp --------------------------------*- C++-*-===//
+//
+// The LLVM Compiler Infrastructure
+//
+// This file is distributed under the University of Illinois Open Source
+// License. See LICENSE.TXT for details.
+//
+//===----------------------------------------------------------------------===//
+#include "RemoveUsingNamespace.h"
+#include "AST.h"
+#include "ClangdUnit.h"
+#include "SourceCode.h"
+#include "clang/AST/RecursiveASTVisitor.h"
+#include "clang/Tooling/Core/Replacement.h"
+#include "clang/Tooling/Refactoring/RecursiveSymbolVisitor.h"
+#include "llvm/ADT/ScopeExit.h"
+
+namespace clang {
+namespace clangd {
+
+namespace {
+class FindNodeUnderCursor : public RecursiveASTVisitor<FindNodeUnderCursor> {
+public:
+ FindNodeUnderCursor(SourceLocation SearchedLoc, UsingDirectiveDecl *&Result)
+ : SearchedLoc(SearchedLoc), Result(Result) {}
+
+ bool VisitUsingDirectiveDecl(UsingDirectiveDecl *D) {
+ if (D->getUsingLoc() != SearchedLoc)
+ return true;
+
+ Result = D;
+ return false;
+ }
+
+private:
+ SourceLocation SearchedLoc;
+ UsingDirectiveDecl *&Result;
+};
+
+class FindSameUsings : public RecursiveASTVisitor<FindSameUsings> {
+public:
+ FindSameUsings(UsingDirectiveDecl &Target,
+ std::vector<UsingDirectiveDecl *> &Results)
+ : TargetNS(Target.getNominatedNamespace()),
+ TargetCtx(Target.getDeclContext()), Results(Results) {}
+
+ bool VisitUsingDirectiveDecl(UsingDirectiveDecl *D) {
+ if (D->getNominatedNamespace() != TargetNS ||
+ D->getDeclContext() != TargetCtx)
+ return true;
+
+ Results.push_back(D);
+ return true;
+ }
+
+private:
+ NamespaceDecl *TargetNS;
+ DeclContext *TargetCtx;
+ std::vector<UsingDirectiveDecl *> &Results;
+};
+
+class FindIdentsToQualify
+ : public tooling::RecursiveSymbolVisitor<FindIdentsToQualify> {
+public:
+ FindIdentsToQualify(ASTContext &Ctx, NamespaceDecl &TargetNS,
+ std::vector<SourceLocation> &ResultIdents)
+ : RecursiveSymbolVisitor(Ctx.getSourceManager(), Ctx.getLangOpts()),
+ Ctx(Ctx), TargetNS(TargetNS), ResultIdents(ResultIdents) {}
+
+ bool visitSymbolOccurrence(const NamedDecl *D,
+ llvm::ArrayRef<SourceRange> NameRanges) {
+ if (!D || D->getCanonicalDecl() == TargetNS.getCanonicalDecl())
+ return true;
+ if (!D->getDeclName().isIdentifier() ||
+ D->getDeclName().getNameKind() == DeclarationName::CXXOperatorName)
+ return true; // do not add qualifiers for non-idents, e.g. 'operator+'.
+ // Check the symbol is unqualified and references something inside our
+ // namespace.
+ // FIXME: add a check it's unqualified.
+ if (!TargetNS.InEnclosingNamespaceSetOf(D->getDeclContext()))
+ return true;
+ // FIXME: handle more tricky cases, e.g. we don't need the qualifier if we
+ // have the using decls for some entities, we might have qualified
+ // references that need updating too.
+ for (auto R : NameRanges) {
+ if (!Ctx.getSourceManager().isWrittenInMainFile(R.getBegin()))
+ continue; // we can't fix refences outside our file.
+ // FIXME: this might be a conflict that we need to report.
+ ResultIdents.push_back(R.getBegin());
+ }
+ return true;
+ }
+
+ bool TraverseDecl(Decl *D) {
+ if (!Ctx.getSourceManager().isWrittenInMainFile(D->getLocation()) &&
+ !isa<TranslationUnitDecl>(D))
+ return true; // skip decls outside main file.
+ return RecursiveSymbolVisitor::TraverseDecl(D);
+ }
+
+private:
+ ASTContext &Ctx;
+ NamespaceDecl &TargetNS;
+ std::vector<SourceLocation> &ResultIdents;
+};
+
+// Produces an edit to remove 'using namespace xxx::yyy' and the trailing
+// semicolon.
+llvm::Expected<tooling::Replacement>
+removeUsingDirective(ASTContext &Ctx, UsingDirectiveDecl *D) {
+ auto &SrcMgr = Ctx.getSourceManager();
+ auto R =
+ Lexer::getAsCharRange(D->getSourceRange(), SrcMgr, Ctx.getLangOpts());
+ if (R.isInvalid())
+ return llvm::createStringError(llvm::inconvertibleErrorCode(),
+ "invalid source range for using "
+ "directive");
+ SourceLocation EndLoc = R.getEnd();
+ auto NextTok = Lexer::findNextToken(EndLoc, SrcMgr, Ctx.getLangOpts());
+ if (!NextTok || NextTok->getKind() != tok::semi)
+ return llvm::createStringError(llvm::inconvertibleErrorCode(),
+ "no semicolon after using-directive");
+ R.setEnd(NextTok->getEndLoc());
+ // FIXME: removing the semicolon may be invalid in some obscure cases, e.g.
+ // if (x) using namespace std; else using namespace bar;
+ return tooling::Replacement(SrcMgr, R, "", Ctx.getLangOpts());
+}
+} // namespace
+ // namespace
+
+llvm::Optional<PreparedAction>
+RemoveUsingNamespace::prepare(const ActionInputs &Inputs) {
+ auto &Ctx = Inputs.AST.getASTContext();
+ // Find the 'using namespace' directive under the cursor.
+ UsingDirectiveDecl *TargetDirective = nullptr;
+ FindNodeUnderCursor(Inputs.Cursor, TargetDirective).TraverseAST(Ctx);
+ if (TargetDirective == nullptr)
+ return None;
+ auto *Parent = dyn_cast<Decl>(TargetDirective->getDeclContext());
+ if (!Parent)
+ return None;
+
+ // The second stage that would deal with the actual removal.
+ auto Do = [&Ctx, TargetDirective,
+ Parent]() -> llvm::Expected<tooling::Replacements> {
+ auto OldScopes = Ctx.getTraversalScope();
+ auto _ = llvm::make_scope_exit([&]() { Ctx.setTraversalScope(OldScopes); });
+ Ctx.setTraversalScope({Parent});
+
+ // First, collect *all* using namespace directives that redeclare the same
+ // namespace.
+ std::vector<UsingDirectiveDecl *> AllDirectives;
+ FindSameUsings(*TargetDirective, AllDirectives).TraverseAST(Ctx);
+ // Collect all references to symbols from the namespace for which we're
+ // removing the directive.
+ std::vector<SourceLocation> IdentsToQualify;
+ FindIdentsToQualify(Ctx, *TargetDirective->getNominatedNamespace(),
+ IdentsToQualify)
+ .TraverseAST(Ctx);
+ // Remove duplicates.
+ llvm::sort(IdentsToQualify);
+ IdentsToQualify.erase(
+ std::unique(IdentsToQualify.begin(), IdentsToQualify.end()),
+ IdentsToQualify.end());
+
+ // Produce replacements to remove the using directives.
+ tooling::Replacements R;
+ for (auto *D : AllDirectives) {
+ auto RemoveUsing = removeUsingDirective(Ctx, D);
+ if (!RemoveUsing)
+ return RemoveUsing.takeError();
+ if (auto Err = R.add(*RemoveUsing))
+ return std::move(Err);
+ }
+ // Produce replacements to add the qualifiers.
+ std::string Qualifier =
+ printUsingNamespaceName(Ctx, *TargetDirective) + "::";
+ for (auto Loc : IdentsToQualify) {
+ if (auto Err = R.add(tooling::Replacement(Ctx.getSourceManager(), Loc,
+ /*Length=*/0, Qualifier)))
+ return std::move(Err);
+ }
+ return R;
+ };
+ return PreparedAction(
+ llvm::formatv("Remove using namespace, add qualifiers instead"), Do);
+}
+} // namespace clangd
+} // namespace clang
Index: clangd/CodeActions.cpp
===================================================================
--- clangd/CodeActions.cpp
+++ clangd/CodeActions.cpp
@@ -10,6 +10,7 @@
#include "Logger.h"
#include "refactor/actions/QualifyName.h"
#include "refactor/actions/SwapIfBranches.h"
+#include "refactor/actions/RemoveUsingNamespace.h"
#include "llvm/ADT/STLExtras.h"
#include "llvm/Support/Error.h"
#include "llvm/Support/FormatVariadic.h"
@@ -24,6 +25,7 @@
// FIXME: provide a registry of the providers.
Actions.push_back(llvm::make_unique<QualifyName>());
Actions.push_back(llvm::make_unique<SwapIfBranches>());
+ Actions.push_back(llvm::make_unique<RemoveUsingNamespace>());
}
std::vector<PreparedAction>
Index: clangd/CMakeLists.txt
===================================================================
--- clangd/CMakeLists.txt
+++ clangd/CMakeLists.txt
@@ -64,6 +64,7 @@
index/dex/Trigram.cpp
refactor/ActionProvider.cpp
+ refactor/actions/RemoveUsingNamespace.cpp
refactor/actions/QualifyName.cpp
refactor/actions/SwapIfBranches.cpp
Index: clangd/AST.h
===================================================================
--- clangd/AST.h
+++ clangd/AST.h
@@ -42,6 +42,12 @@
/// Returns the first enclosing namespace scope starting from \p DC.
std::string printNamespaceScope(const DeclContext &DC);
+/// Returns the name of the namespace inside the 'using namespace' directive, as
+/// written in the code. E.g., passing 'using namespace ::std' will result in
+/// '::std'.
+std::string printUsingNamespaceName(const ASTContext &Ctx,
+ const UsingDirectiveDecl &D);
+
/// Prints unqualified name of the decl for the purpose of displaying it to the
/// user. Anonymous decls return names of the form "(anonymous {kind})", e.g.
/// "(anonymous struct)" or "(anonymous namespace)".
Index: clangd/AST.cpp
===================================================================
--- clangd/AST.cpp
+++ clangd/AST.cpp
@@ -77,16 +77,25 @@
return nullptr;
}
+std::string printUsingNamespaceName(const ASTContext &Ctx,
+ const UsingDirectiveDecl &D) {
+ PrintingPolicy PP(Ctx.getLangOpts());
+ std::string Name;
+ llvm::raw_string_ostream Out(Name);
+
+ if (auto *Qual = D.getQualifier())
+ Qual->print(Out, PP);
+ D.getNominatedNamespaceAsWritten()->printName(Out);
+ return Out.str();
+}
+
std::string printName(const ASTContext &Ctx, const NamedDecl &ND) {
std::string Name;
llvm::raw_string_ostream Out(Name);
PrintingPolicy PP(Ctx.getLangOpts());
// Handle 'using namespace'. They all have the same name - <using-directive>.
if (auto *UD = llvm::dyn_cast<UsingDirectiveDecl>(&ND)) {
- Out << "using namespace ";
- if (auto *Qual = UD->getQualifier())
- Qual->print(Out, PP);
- UD->getNominatedNamespaceAsWritten()->printName(Out);
+ Out << "using namespace " << printUsingNamespaceName(Ctx, *UD);
return Out.str();
}
ND.getDeclName().print(Out, PP);
_______________________________________________
cfe-commits mailing list
[email protected]
http://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits