439 lines · cpp
1//===--- AddUsing.cpp --------------------------------------------*- C++-*-===//2//3// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.4// See https://llvm.org/LICENSE.txt for license information.5// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception6//7//===----------------------------------------------------------------------===//8 9#include "AST.h"10#include "Config.h"11#include "SourceCode.h"12#include "refactor/Tweak.h"13#include "support/Logger.h"14#include "clang/AST/Decl.h"15#include "clang/AST/Expr.h"16#include "clang/AST/NestedNameSpecifier.h"17#include "clang/AST/RecursiveASTVisitor.h"18#include "clang/AST/Type.h"19#include "clang/AST/TypeLoc.h"20#include "clang/Basic/LLVM.h"21#include "clang/Basic/SourceLocation.h"22#include "clang/Tooling/Core/Replacement.h"23#include "clang/Tooling/Syntax/Tokens.h"24#include "llvm/ADT/StringRef.h"25#include "llvm/Support/FormatVariadic.h"26#include "llvm/Support/raw_ostream.h"27#include <string>28#include <tuple>29#include <utility>30 31namespace clang {32namespace clangd {33namespace {34 35// Tweak for removing full namespace qualifier under cursor on DeclRefExpr and36// types and adding "using" statement instead.37//38// Only qualifiers that refer exclusively to namespaces (no record types) are39// supported. There is some guessing of appropriate place to insert the using40// declaration. If we find any existing usings, we insert it there. If not, we41// insert right after the inner-most relevant namespace declaration. If there is42// none, or there is, but it was declared via macro, we insert above the first43// top level decl.44//45// Currently this only removes qualifier from under the cursor. In the future,46// we should improve this to remove qualifier from all occurrences of this47// symbol.48class AddUsing : public Tweak {49public:50 const char *id() const override;51 52 bool prepare(const Selection &Inputs) override;53 Expected<Effect> apply(const Selection &Inputs) override;54 std::string title() const override;55 llvm::StringLiteral kind() const override {56 return CodeAction::REFACTOR_KIND;57 }58 59private:60 // All of the following are set by prepare().61 // The qualifier to remove.62 NestedNameSpecifierLoc QualifierToRemove;63 // Qualified name to use when spelling the using declaration. This might be64 // different than SpelledQualifier in presence of error correction.65 std::string QualifierToSpell;66 // The name and qualifier as spelled in the code.67 llvm::StringRef SpelledQualifier;68 llvm::StringRef SpelledName;69 // If valid, the insertion point for "using" statement must come after this.70 // This is relevant when the type is defined in the main file, to make sure71 // the type/function is already defined at the point where "using" is added.72 SourceLocation MustInsertAfterLoc;73};74REGISTER_TWEAK(AddUsing)75 76std::string AddUsing::title() const {77 return std::string(llvm::formatv(78 "Add using-declaration for {0} and remove qualifier", SpelledName));79}80 81// Locates all "using" statements relevant to SelectionDeclContext.82class UsingFinder : public RecursiveASTVisitor<UsingFinder> {83public:84 UsingFinder(std::vector<const UsingDecl *> &Results,85 const DeclContext *SelectionDeclContext, const SourceManager &SM)86 : Results(Results), SelectionDeclContext(SelectionDeclContext), SM(SM) {}87 88 bool VisitUsingDecl(UsingDecl *D) {89 auto Loc = D->getUsingLoc();90 if (SM.getFileID(Loc) != SM.getMainFileID()) {91 return true;92 }93 if (D->getDeclContext()->Encloses(SelectionDeclContext)) {94 Results.push_back(D);95 }96 return true;97 }98 99 bool TraverseDecl(Decl *Node) {100 if (!Node)101 return true;102 // There is no need to go deeper into nodes that do not enclose selection,103 // since "using" there will not affect selection, nor would it make a good104 // insertion point.105 if (!Node->getDeclContext() ||106 Node->getDeclContext()->Encloses(SelectionDeclContext)) {107 return RecursiveASTVisitor<UsingFinder>::TraverseDecl(Node);108 }109 return true;110 }111 112private:113 std::vector<const UsingDecl *> &Results;114 const DeclContext *SelectionDeclContext;115 const SourceManager &SM;116};117 118struct InsertionPointData {119 // Location to insert the "using" statement. If invalid then the statement120 // should not be inserted at all (it already exists).121 SourceLocation Loc;122 // Extra suffix to place after the "using" statement. Depending on what the123 // insertion point is anchored to, we may need one or more \n to ensure124 // proper formatting.125 std::string Suffix;126 // Whether using should be fully qualified, even if what the user typed was127 // not. This is based on our detection of the local style.128 bool AlwaysFullyQualify = false;129};130 131// Finds the best place to insert the "using" statement. Returns invalid132// SourceLocation if the "using" statement already exists.133//134// The insertion point might be a little awkward if the decl we're anchoring to135// has a comment in an unfortunate place (e.g. directly above function or using136// decl, or immediately following "namespace {". We should add some helpers for137// dealing with that and use them in other code modifications as well.138llvm::Expected<InsertionPointData>139findInsertionPoint(const Tweak::Selection &Inputs,140 const NestedNameSpecifierLoc &QualifierToRemove,141 const llvm::StringRef Name,142 const SourceLocation MustInsertAfterLoc) {143 auto &SM = Inputs.AST->getSourceManager();144 145 // Search for all using decls that affect this point in file. We need this for146 // two reasons: to skip adding "using" if one already exists and to find best147 // place to add it, if it doesn't exist.148 SourceLocation LastUsingLoc;149 std::vector<const UsingDecl *> Usings;150 UsingFinder(Usings, &Inputs.ASTSelection.commonAncestor()->getDeclContext(),151 SM)152 .TraverseAST(Inputs.AST->getASTContext());153 154 auto IsValidPoint = [&](const SourceLocation Loc) {155 return MustInsertAfterLoc.isInvalid() ||156 SM.isBeforeInTranslationUnit(MustInsertAfterLoc, Loc);157 };158 159 bool AlwaysFullyQualify = true;160 for (auto &U : Usings) {161 // Only "upgrade" to fully qualified is all relevant using decls are fully162 // qualified. Otherwise trust what the user typed.163 if (!U->getQualifier().isFullyQualified())164 AlwaysFullyQualify = false;165 166 if (SM.isBeforeInTranslationUnit(Inputs.Cursor, U->getUsingLoc()))167 // "Usings" is sorted, so we're done.168 break;169 if (NestedNameSpecifier Qualifier = U->getQualifier();170 Qualifier.getKind() == NestedNameSpecifier::Kind::Namespace) {171 const auto *Namespace =172 U->getQualifier().getAsNamespaceAndPrefix().Namespace;173 if (Namespace->getCanonicalDecl() ==174 QualifierToRemove.getNestedNameSpecifier()175 .getAsNamespaceAndPrefix()176 .Namespace->getCanonicalDecl() &&177 U->getName() == Name) {178 return InsertionPointData();179 }180 }181 182 // Insertion point will be before last UsingDecl that affects cursor183 // position. For most cases this should stick with the local convention of184 // add using inside or outside namespace.185 LastUsingLoc = U->getUsingLoc();186 }187 if (LastUsingLoc.isValid() && IsValidPoint(LastUsingLoc)) {188 InsertionPointData Out;189 Out.Loc = LastUsingLoc;190 Out.AlwaysFullyQualify = AlwaysFullyQualify;191 return Out;192 }193 194 // No relevant "using" statements. Try the nearest namespace level.195 const DeclContext *ParentDeclCtx =196 &Inputs.ASTSelection.commonAncestor()->getDeclContext();197 while (ParentDeclCtx && !ParentDeclCtx->isFileContext()) {198 ParentDeclCtx = ParentDeclCtx->getLexicalParent();199 }200 if (auto *ND = llvm::dyn_cast_or_null<NamespaceDecl>(ParentDeclCtx)) {201 auto Toks = Inputs.AST->getTokens().expandedTokens(ND->getSourceRange());202 const auto *Tok = llvm::find_if(Toks, [](const syntax::Token &Tok) {203 return Tok.kind() == tok::l_brace;204 });205 if (Tok == Toks.end() || Tok->endLocation().isInvalid()) {206 return error("Namespace with no {{");207 }208 if (!Tok->endLocation().isMacroID() && IsValidPoint(Tok->endLocation())) {209 InsertionPointData Out;210 Out.Loc = Tok->endLocation();211 Out.Suffix = "\n";212 return Out;213 }214 }215 // No using, no namespace, no idea where to insert. Try above the first216 // top level decl after MustInsertAfterLoc.217 auto TLDs = Inputs.AST->getLocalTopLevelDecls();218 for (const auto &TLD : TLDs) {219 if (!IsValidPoint(TLD->getBeginLoc()))220 continue;221 InsertionPointData Out;222 Out.Loc = SM.getExpansionLoc(TLD->getBeginLoc());223 Out.Suffix = "\n\n";224 return Out;225 }226 return error("Cannot find place to insert \"using\"");227}228 229bool isNamespaceForbidden(const Tweak::Selection &Inputs,230 NestedNameSpecifier Namespace) {231 const auto *NS =232 dyn_cast<NamespaceDecl>(Namespace.getAsNamespaceAndPrefix().Namespace);233 if (!NS)234 return true;235 std::string NamespaceStr = printNamespaceScope(*NS);236 237 for (StringRef Banned : Config::current().Style.FullyQualifiedNamespaces) {238 StringRef PrefixMatch = NamespaceStr;239 if (PrefixMatch.consume_front(Banned) && PrefixMatch.consume_front("::"))240 return true;241 }242 243 return false;244}245 246std::string getNNSLAsString(NestedNameSpecifierLoc NNSL,247 const PrintingPolicy &Policy) {248 std::string Out;249 llvm::raw_string_ostream OutStream(Out);250 NNSL.getNestedNameSpecifier().print(OutStream, Policy);251 return OutStream.str();252}253 254bool AddUsing::prepare(const Selection &Inputs) {255 auto &SM = Inputs.AST->getSourceManager();256 const auto &TB = Inputs.AST->getTokens();257 258 // Do not suggest "using" in header files. That way madness lies.259 if (isHeaderFile(SM.getFileEntryRefForID(SM.getMainFileID())->getName(),260 Inputs.AST->getLangOpts()))261 return false;262 263 auto *Node = Inputs.ASTSelection.commonAncestor();264 if (Node == nullptr)265 return false;266 267 // If we're looking at a type or NestedNameSpecifier, walk up the tree until268 // we find the "main" node we care about, which would be ElaboratedTypeLoc or269 // DeclRefExpr.270 for (; Node->Parent; Node = Node->Parent) {271 if (Node->ASTNode.get<NestedNameSpecifierLoc>()) {272 continue;273 }274 if (auto *T = Node->ASTNode.get<TypeLoc>()) {275 // Find the outermost TypeLoc.276 if (Node->Parent->ASTNode.get<NestedNameSpecifierLoc>())277 continue;278 if (isa<TagType, TemplateSpecializationType, TypedefType, UsingType,279 UnresolvedUsingType>(T->getTypePtr()))280 break;281 // Find the outermost TypeLoc.282 if (Node->Parent->ASTNode.get<TypeLoc>())283 continue;284 }285 break;286 }287 if (Node == nullptr)288 return false;289 290 // Closed range for the fully qualified name as spelled in source code.291 SourceRange SpelledNameRange;292 if (auto *D = Node->ASTNode.get<DeclRefExpr>()) {293 if (D->getDecl()->getIdentifier()) {294 QualifierToRemove = D->getQualifierLoc();295 // Use the name range rather than expr, as the latter can contain template296 // arguments in the range.297 SpelledNameRange = D->getSourceRange();298 // Remove the template arguments from the name, as they shouldn't be299 // spelled in the using declaration.300 if (auto AngleLoc = D->getLAngleLoc(); AngleLoc.isValid())301 SpelledNameRange.setEnd(AngleLoc.getLocWithOffset(-1));302 MustInsertAfterLoc = D->getDecl()->getBeginLoc();303 }304 } else if (auto *T = Node->ASTNode.get<TypeLoc>()) {305 switch (T->getTypeLocClass()) {306 case TypeLoc::TemplateSpecialization: {307 auto TL = T->castAs<TemplateSpecializationTypeLoc>();308 QualifierToRemove = TL.getQualifierLoc();309 if (!QualifierToRemove)310 break;311 SpelledNameRange = TL.getTemplateNameLoc();312 if (auto *TD = TL.getTypePtr()->getTemplateName().getAsTemplateDecl(313 /*IgnoreDeduced=*/true))314 MustInsertAfterLoc = TD->getBeginLoc();315 break;316 }317 case TypeLoc::Enum:318 case TypeLoc::Record:319 case TypeLoc::InjectedClassName: {320 auto TL = T->castAs<TagTypeLoc>();321 QualifierToRemove = TL.getQualifierLoc();322 if (!QualifierToRemove)323 break;324 SpelledNameRange = TL.getNameLoc();325 MustInsertAfterLoc = TL.getDecl()->getBeginLoc();326 break;327 }328 case TypeLoc::Typedef: {329 auto TL = T->castAs<TypedefTypeLoc>();330 QualifierToRemove = TL.getQualifierLoc();331 if (!QualifierToRemove)332 break;333 SpelledNameRange = TL.getNameLoc();334 MustInsertAfterLoc = TL.getDecl()->getBeginLoc();335 break;336 }337 case TypeLoc::UnresolvedUsing: {338 auto TL = T->castAs<UnresolvedUsingTypeLoc>();339 QualifierToRemove = TL.getQualifierLoc();340 if (!QualifierToRemove)341 break;342 SpelledNameRange = TL.getNameLoc();343 MustInsertAfterLoc = TL.getDecl()->getBeginLoc();344 break;345 }346 case TypeLoc::Using: {347 auto TL = T->castAs<UsingTypeLoc>();348 QualifierToRemove = TL.getQualifierLoc();349 if (!QualifierToRemove)350 break;351 SpelledNameRange = TL.getNameLoc();352 MustInsertAfterLoc = TL.getDecl()->getBeginLoc();353 break;354 }355 default:356 break;357 }358 if (QualifierToRemove)359 SpelledNameRange.setBegin(QualifierToRemove.getBeginLoc());360 }361 if (!QualifierToRemove ||362 // FIXME: This only supports removing qualifiers that are made up of just363 // namespace names. If qualifier contains a type, we could take the364 // longest namespace prefix and remove that.365 QualifierToRemove.getNestedNameSpecifier().getKind() !=366 NestedNameSpecifier::Kind::Namespace ||367 // Respect user config.368 isNamespaceForbidden(Inputs, QualifierToRemove.getNestedNameSpecifier()))369 return false;370 // Macros are difficult. We only want to offer code action when what's spelled371 // under the cursor is a namespace qualifier. If it's a macro that expands to372 // a qualifier, user would not know what code action will actually change.373 // On the other hand, if the qualifier is part of the macro argument, we374 // should still support that.375 if (SM.isMacroBodyExpansion(QualifierToRemove.getBeginLoc()) ||376 !SM.isWrittenInSameFile(QualifierToRemove.getBeginLoc(),377 QualifierToRemove.getEndLoc())) {378 return false;379 }380 381 auto SpelledTokens =382 TB.spelledForExpanded(TB.expandedTokens(SpelledNameRange));383 if (!SpelledTokens)384 return false;385 auto SpelledRange =386 syntax::Token::range(SM, SpelledTokens->front(), SpelledTokens->back());387 // We only drop qualifiers that're namespaces, so this is safe.388 std::tie(SpelledQualifier, SpelledName) =389 splitQualifiedName(SpelledRange.text(SM));390 QualifierToSpell = getNNSLAsString(391 QualifierToRemove, Inputs.AST->getASTContext().getPrintingPolicy());392 if (!llvm::StringRef(QualifierToSpell).ends_with(SpelledQualifier) ||393 SpelledName.empty())394 return false; // What's spelled doesn't match the qualifier.395 return true;396}397 398Expected<Tweak::Effect> AddUsing::apply(const Selection &Inputs) {399 auto &SM = Inputs.AST->getSourceManager();400 401 tooling::Replacements R;402 if (auto Err = R.add(tooling::Replacement(403 SM, SM.getSpellingLoc(QualifierToRemove.getBeginLoc()),404 SpelledQualifier.size(), ""))) {405 return std::move(Err);406 }407 408 auto InsertionPoint = findInsertionPoint(Inputs, QualifierToRemove,409 SpelledName, MustInsertAfterLoc);410 if (!InsertionPoint) {411 return InsertionPoint.takeError();412 }413 414 if (InsertionPoint->Loc.isValid()) {415 // Add the using statement at appropriate location.416 std::string UsingText;417 llvm::raw_string_ostream UsingTextStream(UsingText);418 UsingTextStream << "using ";419 if (InsertionPoint->AlwaysFullyQualify &&420 !QualifierToRemove.getNestedNameSpecifier().isFullyQualified())421 UsingTextStream << "::";422 UsingTextStream << QualifierToSpell << SpelledName << ";"423 << InsertionPoint->Suffix;424 425 assert(SM.getFileID(InsertionPoint->Loc) == SM.getMainFileID());426 if (auto Err = R.add(tooling::Replacement(SM, InsertionPoint->Loc, 0,427 UsingTextStream.str()))) {428 return std::move(Err);429 }430 }431 432 return Effect::mainFileEdit(Inputs.AST->getASTContext().getSourceManager(),433 std::move(R));434}435 436} // namespace437} // namespace clangd438} // namespace clang439