Index: clang-tools-extra/clangd/refactor/tweaks/CMakeLists.txt =================================================================== --- clang-tools-extra/clangd/refactor/tweaks/CMakeLists.txt +++ clang-tools-extra/clangd/refactor/tweaks/CMakeLists.txt @@ -8,4 +8,5 @@ # $ to a list of sources, see # clangd/tool/CMakeLists.txt for an example. add_clang_library(clangDaemonTweaks OBJECT + QualifyName.cpp ) Index: clang-tools-extra/clangd/refactor/tweaks/QualifyName.cpp =================================================================== --- /dev/null +++ clang-tools-extra/clangd/refactor/tweaks/QualifyName.cpp @@ -0,0 +1,180 @@ +//===--- QualifyName.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/AST/TypeLoc.h" +#include "clang/Basic/SourceLocation.h" +#include "clang/Tooling/Tooling.h" +#include "llvm/Support/Error.h" + +namespace clang { +namespace clangd { +namespace { +/// Fully qualifies a name under a cursor. +/// Before: +/// using namespace std; +/// ^vector foo; +/// After: +/// std::vector foo; +class QualifyName : public Tweak { +public: + TweakID id() const override { return llvm::StringLiteral("qualify-name"); } + + bool prepare(const Selection &Inputs) override; + Expected apply(const Selection &Inputs) override; + std::string title() const override; + +private: + SourceLocation InsertLoc; + std::string Qualifier; +}; + +REGISTER_TWEAK(QualifyName); + +struct Reference { + SourceLocation Begin; + NamedDecl *Target = nullptr; + + operator bool() const { return Target != nullptr; } +}; + +NamedDecl *toReferencedDecl(NestedNameSpecifier *NNS) { + switch (NNS->getKind()) { + case clang::NestedNameSpecifier::Namespace: + return NNS->getAsNamespace(); + case clang::NestedNameSpecifier::NamespaceAlias: + return NNS->getAsNamespaceAlias(); + case clang::NestedNameSpecifier::TypeSpec: + case clang::NestedNameSpecifier::TypeSpecWithTemplate: + return nullptr; // FIXME: handle this situation, retrieve the thing + // referenced inside a type. + case clang::NestedNameSpecifier::Identifier: + case clang::NestedNameSpecifier::Super: + case clang::NestedNameSpecifier::Global: // FIXME: could return + // TranslationUnitDecl. + return nullptr; + return nullptr; + } + llvm_unreachable("unhandled NestedNameSpecifier kind."); +} + +class LocateInsertLoc : public RecursiveASTVisitor { +public: + LocateInsertLoc(ASTContext &Ctx, SourceLocation CursorLoc, + Reference &UnqualRef) + : Ctx(Ctx), CursorLoc(CursorLoc), UnqualRef(UnqualRef) {} + + bool shouldWalkTypesOfTypeLocs() const { return false; } + + // FIXME: RAT does not have VisitNestedNameSpecifierLoc. Should we add that? + bool TraverseNestedNameSpecifierLoc(NestedNameSpecifierLoc NNS) { + if (!RecursiveASTVisitor::TraverseNestedNameSpecifierLoc(NNS)) + return false; + return VisitNestedNameSpecifierLoc(NNS); + } + + bool VisitNestedNameSpecifierLoc(NestedNameSpecifierLoc NNS) { + if (NNS.getPrefix()) + return true; // we want only unqualified names. + auto &SM = Ctx.getSourceManager(); + auto Rng = toHalfOpenFileRange(SM, Ctx.getLangOpts(), NNS.getSourceRange()); + if (!Rng) + return true; + if (!halfOpenRangeContains(SM, *Rng, CursorLoc)) + return true; + auto *Target = toReferencedDecl(NNS.getNestedNameSpecifier()); + if (!Target) + return true; // continue traversal to recurse into types, if any. + UnqualRef.Begin = Rng->getBegin(); + UnqualRef.Target = Target; + return false; // we found the insertion point. + } + + bool VisitDeclRefExpr(DeclRefExpr *E) { + if (E->hasQualifier()) + return true; // we want only unqualified names. + auto &SM = Ctx.getSourceManager(); + auto Rng = toHalfOpenFileRange(SM, Ctx.getLangOpts(), E->getSourceRange()); + if (!Rng) + return true; + if (!halfOpenRangeContains(SM, *Rng, CursorLoc)) + return true; + UnqualRef.Begin = Rng->getBegin(); + UnqualRef.Target = E->getFoundDecl(); + return false; + } + + bool VisitTagTypeLoc(TagTypeLoc Loc) { + auto &SM = Ctx.getSourceManager(); + auto Rng = toHalfOpenFileRange(SM, Ctx.getLangOpts(), Loc.getSourceRange()); + if (!Rng) + return true; + if (!halfOpenRangeContains(SM, *Rng, CursorLoc)) + return true; + UnqualRef.Begin = Rng->getBegin(); + UnqualRef.Target = Loc.getDecl(); + return false; + } + + bool VisitTypedefTypeLoc(TypedefTypeLoc Loc) { + auto &SM = Ctx.getSourceManager(); + auto Rng = toHalfOpenFileRange(SM, Ctx.getLangOpts(), Loc.getSourceRange()); + if (!Rng) + return true; + if (!halfOpenRangeContains(SM, *Rng, CursorLoc)) + return true; + UnqualRef.Begin = Rng->getBegin(); + UnqualRef.Target = Loc.getTypedefNameDecl(); + return false; + } + +private: + ASTContext &Ctx; + SourceLocation CursorLoc; + Reference &UnqualRef; +}; + +bool QualifyName::prepare(const Selection &Inputs) { + auto &Ctx = Inputs.AST.getASTContext(); + Reference Ref; + LocateInsertLoc(Ctx, Inputs.Cursor, Ref).TraverseAST(Ctx); + if (!Ref) + return false; + auto *Parent = Ref.Target->getDeclContext(); + if (!Parent->isNamespace()) + return false; // avoid complicated cases, qualify only with namespaces. + Qualifier = printNamespaceScope(*Parent); + if (Qualifier.empty()) + return false; // happens for decls from global namespaces. + InsertLoc = Ref.Begin; + return true; +} + +Expected QualifyName::apply(const Selection &Inputs) { + auto &Ctx = Inputs.AST.getASTContext(); + + tooling::Replacements R; + if (auto Err = R.add(tooling::Replacement(Ctx.getSourceManager(), InsertLoc, + 0, Qualifier))) { + llvm::consumeError(std::move(Err)); + return llvm::createStringError(llvm::inconvertibleErrorCode(), + "building a replacement failed"); + } + return R; +} + +std::string QualifyName::title() const { + return llvm::formatv("Add '{0}' qualifier", Qualifier); +} +} // namespace +} // namespace clangd +} // namespace clang