brintos

brintos / llvm-project-archived public Read only

0
0
Text · 16.1 KiB · 3f43362 Raw
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