Index: clangd/CMakeLists.txt =================================================================== --- clangd/CMakeLists.txt +++ clangd/CMakeLists.txt @@ -71,6 +71,7 @@ index/dex/Trigram.cpp refactor/Tweak.cpp + refactor/tweaks/QualifyName.cpp LINK_LIBS clangAST Index: clangd/SourceCode.h =================================================================== --- clangd/SourceCode.h +++ clangd/SourceCode.h @@ -15,6 +15,7 @@ #define LLVM_CLANG_TOOLS_EXTRA_CLANGD_SOURCECODE_H #include "Protocol.h" #include "clang/Basic/Diagnostic.h" +#include "clang/Basic/LangOptions.h" #include "clang/Basic/SourceLocation.h" #include "clang/Basic/SourceManager.h" #include "clang/Tooling/Core/Replacement.h" @@ -60,6 +61,30 @@ llvm::Expected positionToSourceLoc(const SourceManager &SM, Position P); +/// Turns a token range into a half-open range and checks its correctness. +/// The resulting range will have only valid source location on both sides, both +/// of which are file locations. +llvm::Optional toHalfOpenFileRange(const SourceManager &Mgr, + const LangOptions &LangOpts, + SourceRange R); + +/// Returns true iff all of the following conditions hold: +/// - start and end locations are valid, +/// - start and end locations are file locations from the same file +/// (i.e. expansion locations are not taken into account). +/// - start offset <= end offset. +/// FIXME: introduce a type for source range with this invariant. +bool isValidFileRange(const SourceManager &Mgr, SourceRange R); + +/// Returns true iff \p L is contained in \p R. +/// Preconditions: isValidFileRange(R) == true, L is a file location. +bool halfOpenRangeContains(const SourceManager &Mgr, SourceRange R, + SourceLocation L); + +/// Returns the source code covered by the source range. +/// Preconditions: isValidRange(R) == true. +llvm::StringRef toSourceCode(const SourceManager &SM, SourceRange R); + // Converts a half-open clang source range to an LSP range. // Note that clang also uses closed source ranges, which this can't handle! Range halfOpenToRange(const SourceManager &SM, CharSourceRange R); Index: clangd/SourceCode.cpp =================================================================== --- clangd/SourceCode.cpp +++ clangd/SourceCode.cpp @@ -12,6 +12,8 @@ #include "clang/AST/ASTContext.h" #include "clang/Basic/SourceManager.h" #include "clang/Lex/Lexer.h" +#include "llvm/ADT/None.h" +#include "llvm/ADT/StringRef.h" #include "llvm/Support/Errc.h" #include "llvm/Support/Error.h" #include "llvm/Support/Path.h" @@ -142,6 +144,64 @@ return P; } +bool isValidFileRange(const SourceManager &Mgr, SourceRange R) { + if (!R.getBegin().isValid() || !R.getEnd().isValid()) + return false; + + FileID BeginFID; + size_t BeginOffset = 0; + std::tie(BeginFID, BeginOffset) = Mgr.getDecomposedLoc(R.getBegin()); + + FileID EndFID; + size_t EndOffset = 0; + std::tie(EndFID, EndOffset) = Mgr.getDecomposedLoc(R.getEnd()); + + return BeginFID.isValid() && BeginFID == EndFID && BeginOffset <= EndOffset; +} + +bool halfOpenRangeContains(const SourceManager &Mgr, SourceRange R, + SourceLocation L) { + assert(isValidFileRange(Mgr, R)); + + FileID BeginFID; + size_t BeginOffset = 0; + std::tie(BeginFID, BeginOffset) = Mgr.getDecomposedLoc(R.getBegin()); + size_t EndOffset = Mgr.getFileOffset(R.getEnd()); + + FileID LFid; + size_t LOffset; + std::tie(LFid, LOffset) = Mgr.getDecomposedLoc(L); + return BeginFID == LFid && BeginOffset <= LOffset && LOffset < EndOffset; +} + +llvm::Optional toHalfOpenFileRange(const SourceManager &Mgr, + const LangOptions &LangOpts, + SourceRange R) { + auto Begin = Mgr.getFileLoc(R.getBegin()); + if (Begin.isInvalid()) + return llvm::None; + auto End = Mgr.getFileLoc(R.getEnd()); + if (End.isInvalid()) + return llvm::None; + End = Lexer::getLocForEndOfToken(End, 0, Mgr, LangOpts); + + SourceRange Result(Begin, End); + if (!isValidFileRange(Mgr, Result)) + return llvm::None; + return Result; +} + +llvm::StringRef toSourceCode(const SourceManager &SM, SourceRange R) { + assert(isValidFileRange(SM, R)); + bool Invalid = false; + auto *Buf = SM.getBuffer(SM.getFileID(R.getBegin()), &Invalid); + assert(!Invalid); + + size_t BeginOffset = SM.getFileOffset(R.getBegin()); + size_t EndOffset = SM.getFileOffset(R.getEnd()); + return Buf->getBuffer().substr(BeginOffset, EndOffset - BeginOffset); +} + llvm::Expected positionToSourceLoc(const SourceManager &SM, Position P) { llvm::StringRef Code = SM.getBuffer(SM.getMainFileID())->getBuffer(); @@ -170,8 +230,7 @@ return {Lines + 1, Offset - StartOfLine + 1}; } -std::pair -splitQualifiedName(llvm::StringRef QName) { +std::pair splitQualifiedName(StringRef QName) { size_t Pos = QName.rfind("::"); if (Pos == llvm::StringRef::npos) return {llvm::StringRef(), QName}; Index: clangd/refactor/tweaks/QualifyName.h =================================================================== --- /dev/null +++ clangd/refactor/tweaks/QualifyName.h @@ -0,0 +1,42 @@ +//===--- QualifyName.h -------------------------------------------*- C++-*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +// Fully qualifies a name under a cursor. +// Before: +// using namespace std; +// ^vector foo; +// After: +// std::vector foo; +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANGD_REFACTOR_TWEAKS_QUALIFYNAME_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANGD_REFACTOR_TWEAKS_QUALIFYNAME_H + +#include "refactor/Tweak.h" +#include "llvm/ADT/StringRef.h" + +namespace clang { +namespace clangd { + +class QualifyName : public Tweak { +public: + TweakID id() 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; +}; + +} // namespace clangd +} // namespace clang + +#endif \ No newline at end of file Index: clangd/refactor/tweaks/QualifyName.cpp =================================================================== --- /dev/null +++ clangd/refactor/tweaks/QualifyName.cpp @@ -0,0 +1,163 @@ +//===--- 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 "QualifyName.h" +#include "AST.h" +#include "ClangdUnit.h" +#include "SourceCode.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 { + +REGISTER_TWEAK(QualifyName); + +namespace { +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; +}; +} // namespace + +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 clangd +} // namespace clang