ilya-biryukov updated this revision to Diff 182322. ilya-biryukov added a comment.
- Remove the header file Repository: rCTE Clang Tools Extra CHANGES SINCE LAST ACTION https://reviews.llvm.org/D56612/new/ https://reviews.llvm.org/D56612 Files: clangd/AST.cpp clangd/AST.h clangd/refactor/tweaks/CMakeLists.txt clangd/refactor/tweaks/RemoveUsingNamespace.cpp
Index: clangd/refactor/tweaks/RemoveUsingNamespace.cpp =================================================================== --- /dev/null +++ clangd/refactor/tweaks/RemoveUsingNamespace.cpp @@ -0,0 +1,206 @@ +//===--- 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 "AST.h" +#include "ClangdUnit.h" +#include "SourceCode.h" +#include "refactor/Tweak.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 { +/// 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>); +class RemoveUsingNamespace : public Tweak { +public: + TweakID id() const override { + return llvm::StringLiteral("remove-using-namespace"); + } + + bool prepare(const Selection &Inputs) override; + Expected<tooling::Replacements> apply(const Selection &Inputs) override; + std::string title() const override; + +private: + UsingDirectiveDecl *TargetDirective = nullptr; +}; +REGISTER_TWEAK(RemoveUsingNamespace); + +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 + +bool RemoveUsingNamespace::prepare(const Selection &Inputs) { + auto &Ctx = Inputs.AST.getASTContext(); + // Find the 'using namespace' directive under the cursor. + TargetDirective = nullptr; + FindNodeUnderCursor(Inputs.Cursor, TargetDirective).TraverseAST(Ctx); + if (!TargetDirective) + return false; + auto *Parent = dyn_cast<Decl>(TargetDirective->getDeclContext()); + if (!Parent) + return false; + return true; +} + +Expected<tooling::Replacements> +RemoveUsingNamespace::apply(const Selection &Inputs) { + auto &Ctx = Inputs.AST.getASTContext(); + // 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; +} + +std::string RemoveUsingNamespace::title() const { + return llvm::formatv("Remove using namespace, add qualifiers instead"); +} +} // namespace clangd +} // namespace clang Index: clangd/refactor/tweaks/CMakeLists.txt =================================================================== --- clangd/refactor/tweaks/CMakeLists.txt +++ clangd/refactor/tweaks/CMakeLists.txt @@ -9,5 +9,6 @@ # clangd/tool/CMakeLists.txt for an example. add_clang_library(clangDaemonTweaks OBJECT QualifyName.cpp + RemoveUsingNamespace.cpp 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 cfe-commits@lists.llvm.org http://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits