--- /dev/null
+//===--- AddUsing.cpp --------------------------------------------*- C++-*-===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#include "AST.h"
+#include "FindTarget.h"
+#include "Logger.h"
+#include "refactor/Tweak.h"
+#include "clang/AST/Decl.h"
+#include "clang/AST/RecursiveASTVisitor.h"
+
+namespace clang {
+namespace clangd {
+namespace {
+
+// Tweak for removing full namespace qualifier under cursor on DeclRefExpr and
+// types and adding "using" statement instead.
+//
+// Only qualifiers that refer exclusively to namespaces (no record types) are
+// supported. There is some guessing of appropriate place to insert the using
+// declaration. If we find any existing usings, we insert it there. If not, we
+// insert right after the inner-most relevant namespace declaration. If there is
+// none, or there is, but it was declared via macro, we insert above the first
+// top level decl.
+//
+// Currently this only removes qualifier from under the cursor. In the future,
+// we should improve this to remove qualifier from all occurences of this
+// symbol.
+class AddUsing : public Tweak {
+public:
+ const char *id() const override;
+
+ bool prepare(const Selection &Inputs) override;
+ Expected<Effect> apply(const Selection &Inputs) override;
+ std::string title() const override;
+ Intent intent() const override { return Refactor; }
+
+private:
+ // The qualifier to remove. Set by prepare().
+ NestedNameSpecifierLoc QualifierToRemove;
+ // The name following QualifierToRemove. Set by prepare().
+ llvm::StringRef Name;
+};
+REGISTER_TWEAK(AddUsing)
+
+std::string AddUsing::title() const {
+ return std::string(llvm::formatv(
+ "Add using-declaration for {0} and remove qualifier.", Name));
+}
+
+// Locates all "using" statements relevant to SelectionDeclContext.
+class UsingFinder : public RecursiveASTVisitor<UsingFinder> {
+public:
+ UsingFinder(std::vector<const UsingDecl *> &Results,
+ const DeclContext *SelectionDeclContext, const SourceManager &SM)
+ : Results(Results), SelectionDeclContext(SelectionDeclContext), SM(SM) {}
+
+ bool VisitUsingDecl(UsingDecl *D) {
+ auto Loc = D->getUsingLoc();
+ if (SM.getFileID(Loc) != SM.getMainFileID()) {
+ return true;
+ }
+ if (D->getDeclContext()->Encloses(SelectionDeclContext)) {
+ Results.push_back(D);
+ }
+ return true;
+ }
+
+ bool TraverseDecl(Decl *Node) {
+ // There is no need to go deeper into nodes that do not enclose selection,
+ // since "using" there will not affect selection, nor would it make a good
+ // insertion point.
+ if (Node->getDeclContext()->Encloses(SelectionDeclContext)) {
+ return RecursiveASTVisitor<UsingFinder>::TraverseDecl(Node);
+ }
+ return true;
+ }
+
+private:
+ std::vector<const UsingDecl *> &Results;
+ const DeclContext *SelectionDeclContext;
+ const SourceManager &SM;
+};
+
+struct InsertionPointData {
+ // Location to insert the "using" statement. If invalid then the statement
+ // should not be inserted at all (it already exists).
+ SourceLocation Loc;
+ // Extra suffix to place after the "using" statement. Depending on what the
+ // insertion point is anchored to, we may need one or more \n to ensure
+ // proper formatting.
+ std::string Suffix;
+};
+
+// Finds the best place to insert the "using" statement. Returns invalid
+// SourceLocation if the "using" statement already exists.
+//
+// The insertion point might be a little awkward if the decl we're anchoring to
+// has a comment in an unfortunate place (e.g. directly above function or using
+// decl, or immediately following "namespace {". We should add some helpers for
+// dealing with that and use them in other code modifications as well.
+llvm::Expected<InsertionPointData>
+findInsertionPoint(const Tweak::Selection &Inputs,
+ const NestedNameSpecifierLoc &QualifierToRemove,
+ const llvm::StringRef Name) {
+ auto &SM = Inputs.AST->getSourceManager();
+
+ // Search for all using decls that affect this point in file. We need this for
+ // two reasons: to skip adding "using" if one already exists and to find best
+ // place to add it, if it doesn't exist.
+ SourceLocation LastUsingLoc;
+ std::vector<const UsingDecl *> Usings;
+ UsingFinder(Usings, &Inputs.ASTSelection.commonAncestor()->getDeclContext(),
+ SM)
+ .TraverseAST(Inputs.AST->getASTContext());
+
+ for (auto &U : Usings) {
+ if (SM.isBeforeInTranslationUnit(Inputs.Cursor, U->getUsingLoc()))
+ // "Usings" is sorted, so we're done.
+ break;
+ if (U->getQualifier()->getAsNamespace()->getCanonicalDecl() ==
+ QualifierToRemove.getNestedNameSpecifier()
+ ->getAsNamespace()
+ ->getCanonicalDecl() &&
+ U->getName() == Name) {
+ return InsertionPointData();
+ }
+ // Insertion point will be before last UsingDecl that affects cursor
+ // position. For most cases this should stick with the local convention of
+ // add using inside or outside namespace.
+ LastUsingLoc = U->getUsingLoc();
+ }
+ if (LastUsingLoc.isValid()) {
+ InsertionPointData Out;
+ Out.Loc = LastUsingLoc;
+ return Out;
+ }
+
+ // No relevant "using" statements. Try the nearest namespace level.
+ const auto *NS = Inputs.ASTSelection.commonAncestor()
+ ->getDeclContext()
+ .getEnclosingNamespaceContext();
+ if (auto *ND = dyn_cast<NamespaceDecl>(NS)) {
+ auto Toks = Inputs.AST->getTokens().expandedTokens(ND->getSourceRange());
+ const auto *Tok = llvm::find_if(Toks, [](const syntax::Token &Tok) {
+ return Tok.kind() == tok::l_brace;
+ });
+ if (Tok == Toks.end() || Tok->endLocation().isInvalid()) {
+ return llvm::createStringError(llvm::inconvertibleErrorCode(),
+ "Namespace with no {");
+ }
+ if (!Tok->endLocation().isMacroID()) {
+ InsertionPointData Out;
+ Out.Loc = Tok->endLocation();
+ Out.Suffix = "\n";
+ return Out;
+ }
+ }
+ // No using, no namespace, no idea where to insert. Try above the first
+ // top level decl.
+ auto TLDs = Inputs.AST->getLocalTopLevelDecls();
+ if (TLDs.empty()) {
+ return llvm::createStringError(llvm::inconvertibleErrorCode(),
+ "Cannot find place to insert \"using\"");
+ }
+ InsertionPointData Out;
+ Out.Loc = SM.getExpansionLoc(TLDs[0]->getBeginLoc());
+ Out.Suffix = "\n\n";
+ return Out;
+}
+
+bool AddUsing::prepare(const Selection &Inputs) {
+ auto &SM = Inputs.AST->getSourceManager();
+ auto *Node = Inputs.ASTSelection.commonAncestor();
+ if (Node == nullptr)
+ return false;
+
+ // If we're looking at a type or NestedNameSpecifier, walk up the tree until
+ // we find the "main" node we care about, which would be ElaboratedTypeLoc or
+ // DeclRefExpr.
+ for (; Node->Parent; Node = Node->Parent) {
+ if (Node->ASTNode.get<NestedNameSpecifierLoc>()) {
+ continue;
+ } else if (auto *T = Node->ASTNode.get<TypeLoc>()) {
+ if (T->getAs<ElaboratedTypeLoc>()) {
+ break;
+ } else if (Node->Parent->ASTNode.get<TypeLoc>() ||
+ Node->Parent->ASTNode.get<NestedNameSpecifierLoc>()) {
+ // Node is TypeLoc, but it's parent is either TypeLoc or
+ // NestedNameSpecifier. In both cases, we want to go up, to find
+ // the outermost TypeLoc.
+ continue;
+ }
+ }
+ break;
+ }
+ if (Node == nullptr)
+ return false;
+
+ if (auto *D = Node->ASTNode.get<DeclRefExpr>()) {
+ QualifierToRemove = D->getQualifierLoc();
+ Name = D->getDecl()->getName();
+ } else if (auto *T = Node->ASTNode.get<TypeLoc>()) {
+ if (auto E = T->getAs<ElaboratedTypeLoc>()) {
+ QualifierToRemove = E.getQualifierLoc();
+ Name =
+ E.getType().getUnqualifiedType().getBaseTypeIdentifier()->getName();
+ }
+ }
+
+ // FIXME: This only supports removing qualifiers that are made up of just
+ // namespace names. If qualifier contains a type, we could take the longest
+ // namespace prefix and remove that.
+ if (!QualifierToRemove.hasQualifier() ||
+ !QualifierToRemove.getNestedNameSpecifier()->getAsNamespace() ||
+ Name.empty()) {
+ return false;
+ }
+
+ // Macros are difficult. We only want to offer code action when what's spelled
+ // under the cursor is a namespace qualifier. If it's a macro that expands to
+ // a qualifier, user would not know what code action will actually change.
+ // On the other hand, if the qualifier is part of the macro argument, we
+ // should still support that.
+ if (SM.isMacroBodyExpansion(QualifierToRemove.getBeginLoc()) ||
+ !SM.isWrittenInSameFile(QualifierToRemove.getBeginLoc(),
+ QualifierToRemove.getEndLoc())) {
+ return false;
+ }
+
+ return true;
+}
+
+Expected<Tweak::Effect> AddUsing::apply(const Selection &Inputs) {
+ auto &SM = Inputs.AST->getSourceManager();
+ auto &TB = Inputs.AST->getTokens();
+
+ // Determine the length of the qualifier under the cursor, then remove it.
+ auto SpelledTokens = TB.spelledForExpanded(
+ TB.expandedTokens(QualifierToRemove.getSourceRange()));
+ if (!SpelledTokens) {
+ return llvm::createStringError(
+ llvm::inconvertibleErrorCode(),
+ "Could not determine length of the qualifier");
+ }
+ unsigned Length =
+ syntax::Token::range(SM, SpelledTokens->front(), SpelledTokens->back())
+ .length();
+ tooling::Replacements R;
+ if (auto Err = R.add(tooling::Replacement(
+ SM, SpelledTokens->front().location(), Length, ""))) {
+ return std::move(Err);
+ }
+
+ auto InsertionPoint = findInsertionPoint(Inputs, QualifierToRemove, Name);
+ if (!InsertionPoint) {
+ return InsertionPoint.takeError();
+ }
+
+ if (InsertionPoint->Loc.isValid()) {
+ // Add the using statement at appropriate location.
+ std::string UsingText;
+ llvm::raw_string_ostream UsingTextStream(UsingText);
+ UsingTextStream << "using ";
+ QualifierToRemove.getNestedNameSpecifier()->print(
+ UsingTextStream, Inputs.AST->getASTContext().getPrintingPolicy());
+ UsingTextStream << Name << ";" << InsertionPoint->Suffix;
+
+ assert(SM.getFileID(InsertionPoint->Loc) == SM.getMainFileID());
+ if (auto Err = R.add(tooling::Replacement(SM, InsertionPoint->Loc, 0,
+ UsingTextStream.str()))) {
+ return std::move(Err);
+ }
+ }
+
+ return Effect::mainFileEdit(Inputs.AST->getASTContext().getSourceManager(),
+ std::move(R));
+}
+
+} // namespace
+} // namespace clangd
+} // namespace clang
EXPECT_EQ(apply(Case.first), Case.second);
}
}
+
+TWEAK_TEST(AddUsing);
+TEST_F(AddUsingTest, Prepare) {
+ const std::string Header = R"cpp(
+#define NS(name) one::two::name
+namespace one {
+void oo() {}
+namespace two {
+enum ee {};
+void ff() {}
+class cc {
+public:
+ struct st {};
+ static void mm() {}
+};
+}
+})cpp";
+
+ EXPECT_AVAILABLE(Header + "void fun() { o^n^e^:^:^t^w^o^:^:^f^f(); }");
+ EXPECT_AVAILABLE(Header + "void fun() { o^n^e^::^o^o(); }");
+ EXPECT_AVAILABLE(Header + "void fun() { o^n^e^:^:^t^w^o^:^:^e^e E; }");
+ EXPECT_AVAILABLE(Header + "void fun() { o^n^e^:^:^t^w^o:^:^c^c C; }");
+ EXPECT_UNAVAILABLE(Header +
+ "void fun() { o^n^e^:^:^t^w^o^:^:^c^c^:^:^m^m(); }");
+ EXPECT_UNAVAILABLE(Header +
+ "void fun() { o^n^e^:^:^t^w^o^:^:^c^c^:^:^s^t inst; }");
+ EXPECT_UNAVAILABLE(Header +
+ "void fun() { o^n^e^:^:^t^w^o^:^:^c^c^:^:^s^t inst; }");
+ EXPECT_UNAVAILABLE(Header + "void fun() { N^S(c^c) inst; }");
+}
+
+TEST_F(AddUsingTest, Apply) {
+ FileName = "test.cpp";
+ struct {
+ llvm::StringRef TestSource;
+ llvm::StringRef ExpectedSource;
+ } Cases[]{{
+ // Function, no other using, namespace.
+ R"cpp(
+#include "test.hpp"
+namespace {
+void fun() {
+ ^o^n^e^:^:^t^w^o^:^:^f^f();
+}
+})cpp",
+ R"cpp(
+#include "test.hpp"
+namespace {using one::two::ff;
+
+void fun() {
+ ff();
+}
+})cpp",
+ },
+ // Type, no other using, namespace.
+ {
+ R"cpp(
+#include "test.hpp"
+namespace {
+void fun() {
+ ::on^e::t^wo::c^c inst;
+}
+})cpp",
+ R"cpp(
+#include "test.hpp"
+namespace {using ::one::two::cc;
+
+void fun() {
+ cc inst;
+}
+})cpp",
+ },
+ // Type, no other using, no namespace.
+ {
+ R"cpp(
+#include "test.hpp"
+
+void fun() {
+ on^e::t^wo::e^e inst;
+})cpp",
+ R"cpp(
+#include "test.hpp"
+
+using one::two::ee;
+
+void fun() {
+ ee inst;
+})cpp"},
+ // Function, other usings.
+ {
+ R"cpp(
+#include "test.hpp"
+
+using one::two::cc;
+using one::two::ee;
+
+namespace {
+void fun() {
+ one::two::f^f();
+}
+})cpp",
+ R"cpp(
+#include "test.hpp"
+
+using one::two::cc;
+using one::two::ff;using one::two::ee;
+
+namespace {
+void fun() {
+ ff();
+}
+})cpp",
+ },
+ // Function, other usings inside namespace.
+ {
+ R"cpp(
+#include "test.hpp"
+
+using one::two::cc;
+
+namespace {
+
+using one::two::ff;
+
+void fun() {
+ o^ne::o^o();
+}
+})cpp",
+ R"cpp(
+#include "test.hpp"
+
+using one::two::cc;
+
+namespace {
+
+using one::oo;using one::two::ff;
+
+void fun() {
+ oo();
+}
+})cpp"},
+ // Using comes after cursor.
+ {
+ R"cpp(
+#include "test.hpp"
+
+namespace {
+
+void fun() {
+ one::t^wo::ff();
+}
+
+using one::two::cc;
+
+})cpp",
+ R"cpp(
+#include "test.hpp"
+
+namespace {using one::two::ff;
+
+
+void fun() {
+ ff();
+}
+
+using one::two::cc;
+
+})cpp"},
+ // Pointer type.
+ {R"cpp(
+#include "test.hpp"
+
+void fun() {
+ one::two::c^c *p;
+})cpp",
+ R"cpp(
+#include "test.hpp"
+
+using one::two::cc;
+
+void fun() {
+ cc *p;
+})cpp"},
+ // Namespace declared via macro.
+ {R"cpp(
+#include "test.hpp"
+#define NS_BEGIN(name) namespace name {
+
+NS_BEGIN(foo)
+
+void fun() {
+ one::two::f^f();
+}
+})cpp",
+ R"cpp(
+#include "test.hpp"
+#define NS_BEGIN(name) namespace name {
+
+using one::two::ff;
+
+NS_BEGIN(foo)
+
+void fun() {
+ ff();
+}
+})cpp"},
+ // Inside macro argument.
+ {R"cpp(
+#include "test.hpp"
+#define CALL(name) name()
+
+void fun() {
+ CALL(one::t^wo::ff);
+})cpp",
+ R"cpp(
+#include "test.hpp"
+#define CALL(name) name()
+
+using one::two::ff;
+
+void fun() {
+ CALL(ff);
+})cpp"}};
+ llvm::StringMap<std::string> EditedFiles;
+ for (const auto &Case : Cases) {
+ for (const auto &SubCase : expandCases(Case.TestSource)) {
+ ExtraFiles["test.hpp"] = R"cpp(
+namespace one {
+void oo() {}
+namespace two {
+enum ee {};
+void ff() {}
+class cc {
+public:
+ struct st { struct nested {}; };
+ static void mm() {}
+};
+}
+})cpp";
+ EXPECT_EQ(apply(SubCase, &EditedFiles), Case.ExpectedSource);
+ }
+ }
+}
+
} // namespace
} // namespace clangd
} // namespace clang