brintos

brintos / llvm-project-archived public Read only

0
0
Text · 8.2 KiB · 4408080 Raw
247 lines · cpp
1//===--- ScopifyEnum.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 "ParsedAST.h"10#include "Protocol.h"11#include "Selection.h"12#include "SourceCode.h"13#include "XRefs.h"14#include "refactor/Tweak.h"15#include "clang/AST/Decl.h"16#include "clang/AST/DeclBase.h"17#include "clang/Basic/LLVM.h"18#include "clang/Basic/SourceLocation.h"19#include "clang/Basic/SourceManager.h"20#include "clang/Tooling/Core/Replacement.h"21#include "llvm/ADT/SmallVector.h"22#include "llvm/ADT/StringMap.h"23#include "llvm/ADT/StringRef.h"24#include "llvm/Support/Error.h"25#include "llvm/Support/MemoryBuffer.h"26 27#include <cstddef>28#include <functional>29#include <memory>30#include <string>31#include <utility>32 33namespace clang::clangd {34namespace {35 36/// Turns an unscoped into a scoped enum type.37/// Before:38///   enum E { EV1, EV2 };39///        ^40///   void f() { E e1 = EV1; }41///42/// After:43///   enum class E { V1, V2 };44///   void f() { E e1 = E::V1; }45///46/// Note that the respective project code might not compile anymore47/// if it made use of the now-gone implicit conversion to int.48/// This is out of scope for this tweak.49 50class ScopifyEnum : public Tweak {51  const char *id() const final;52  std::string title() const override { return "Convert to scoped enum"; }53  llvm::StringLiteral kind() const override {54    return CodeAction::REFACTOR_KIND;55  }56  bool prepare(const Selection &Inputs) override;57  Expected<Tweak::Effect> apply(const Selection &Inputs) override;58 59  using MakeReplacement =60      std::function<tooling::Replacement(StringRef, StringRef, unsigned)>;61  llvm::Error addClassKeywordToDeclarations();62  llvm::Error scopifyEnumValues();63  llvm::Error scopifyEnumValue(const EnumConstantDecl &CD, StringRef EnumName,64                               bool StripPrefix);65  llvm::Expected<StringRef> getContentForFile(StringRef FilePath);66  llvm::Error addReplacementForReference(const ReferencesResult::Reference &Ref,67                                         const MakeReplacement &GetReplacement);68  llvm::Error addReplacement(StringRef FilePath, StringRef Content,69                             const tooling::Replacement &Replacement);70 71  const EnumDecl *D = nullptr;72  const Selection *S = nullptr;73  SourceManager *SM = nullptr;74  llvm::SmallVector<std::unique_ptr<llvm::MemoryBuffer>> ExtraBuffers;75  llvm::StringMap<StringRef> ContentPerFile;76  Effect E;77};78 79REGISTER_TWEAK(ScopifyEnum)80 81bool ScopifyEnum::prepare(const Selection &Inputs) {82  if (!Inputs.AST->getLangOpts().CPlusPlus11)83    return false;84  const SelectionTree::Node *N = Inputs.ASTSelection.commonAncestor();85  if (!N)86    return false;87  D = N->ASTNode.get<EnumDecl>();88  return D && !D->isScoped() && D->isThisDeclarationADefinition();89}90 91Expected<Tweak::Effect> ScopifyEnum::apply(const Selection &Inputs) {92  S = &Inputs;93  SM = &S->AST->getSourceManager();94  E.FormatEdits = false;95  ContentPerFile.insert(std::make_pair(SM->getFilename(D->getLocation()),96                                       SM->getBufferData(SM->getMainFileID())));97 98  if (auto Err = addClassKeywordToDeclarations())99    return std::move(Err);100  if (auto Err = scopifyEnumValues())101    return std::move(Err);102 103  return E;104}105 106llvm::Error ScopifyEnum::addClassKeywordToDeclarations() {107  for (const auto &Ref :108       findReferences(*S->AST, sourceLocToPosition(*SM, D->getBeginLoc()), 0,109                      S->Index, false)110           .References) {111    if (!(Ref.Attributes & ReferencesResult::Declaration))112      continue;113 114    static const auto MakeReplacement = [](StringRef FilePath,115                                           StringRef Content, unsigned Offset) {116      return tooling::Replacement(FilePath, Offset, 0, "class ");117    };118    if (auto Err = addReplacementForReference(Ref, MakeReplacement))119      return Err;120  }121  return llvm::Error::success();122}123 124llvm::Error ScopifyEnum::scopifyEnumValues() {125  StringRef EnumName(D->getName());126  bool StripPrefix = true;127  for (const EnumConstantDecl *E : D->enumerators()) {128    if (!E->getName().starts_with(EnumName)) {129      StripPrefix = false;130      break;131    }132  }133  for (const EnumConstantDecl *E : D->enumerators()) {134    if (auto Err = scopifyEnumValue(*E, EnumName, StripPrefix))135      return Err;136  }137  return llvm::Error::success();138}139 140llvm::Error ScopifyEnum::scopifyEnumValue(const EnumConstantDecl &CD,141                                          StringRef EnumName,142                                          bool StripPrefix) {143  for (const auto &Ref :144       findReferences(*S->AST, sourceLocToPosition(*SM, CD.getBeginLoc()), 0,145                      S->Index, false)146           .References) {147    if (Ref.Attributes & ReferencesResult::Declaration) {148      if (StripPrefix) {149        const auto MakeReplacement = [&EnumName](StringRef FilePath,150                                                 StringRef Content,151                                                 unsigned Offset) {152          unsigned Length = EnumName.size();153          if (Content[Offset + Length] == '_')154            ++Length;155          return tooling::Replacement(FilePath, Offset, Length, {});156        };157        if (auto Err = addReplacementForReference(Ref, MakeReplacement))158          return Err;159      }160      continue;161    }162 163    const auto MakeReplacement = [&](StringRef FilePath, StringRef Content,164                                     unsigned Offset) {165      const auto IsAlreadyScoped = [Content, Offset] {166        if (Offset < 2)167          return false;168        unsigned I = Offset;169        while (--I > 0) {170          switch (Content[I]) {171          case ' ':172          case '\t':173          case '\n':174            continue;175          case ':':176            if (Content[I - 1] == ':')177              return true;178            [[fallthrough]];179          default:180            return false;181          }182        }183        return false;184      };185      if (StripPrefix) {186        const int ExtraLength =187            Content[Offset + EnumName.size()] == '_' ? 1 : 0;188        if (IsAlreadyScoped())189          return tooling::Replacement(FilePath, Offset,190                                      EnumName.size() + ExtraLength, {});191        return tooling::Replacement(FilePath, Offset + EnumName.size(),192                                    ExtraLength, "::");193      }194      return IsAlreadyScoped() ? tooling::Replacement()195                               : tooling::Replacement(FilePath, Offset, 0,196                                                      EnumName.str() + "::");197    };198    if (auto Err = addReplacementForReference(Ref, MakeReplacement))199      return Err;200  }201 202  return llvm::Error::success();203}204 205llvm::Expected<StringRef> ScopifyEnum::getContentForFile(StringRef FilePath) {206  if (auto It = ContentPerFile.find(FilePath); It != ContentPerFile.end())207    return It->second;208  auto Buffer = S->FS->getBufferForFile(FilePath);209  if (!Buffer)210    return llvm::errorCodeToError(Buffer.getError());211  StringRef Content = Buffer->get()->getBuffer();212  ExtraBuffers.push_back(std::move(*Buffer));213  ContentPerFile.insert(std::make_pair(FilePath, Content));214  return Content;215}216 217llvm::Error218ScopifyEnum::addReplacementForReference(const ReferencesResult::Reference &Ref,219                                        const MakeReplacement &GetReplacement) {220  StringRef FilePath = Ref.Loc.uri.file();221  llvm::Expected<StringRef> Content = getContentForFile(FilePath);222  if (!Content)223    return Content.takeError();224  llvm::Expected<size_t> Offset =225      positionToOffset(*Content, Ref.Loc.range.start);226  if (!Offset)227    return Offset.takeError();228  tooling::Replacement Replacement =229      GetReplacement(FilePath, *Content, *Offset);230  if (Replacement.isApplicable())231    return addReplacement(FilePath, *Content, Replacement);232  return llvm::Error::success();233}234 235llvm::Error236ScopifyEnum::addReplacement(StringRef FilePath, StringRef Content,237                            const tooling::Replacement &Replacement) {238  Edit &TheEdit = E.ApplyEdits[FilePath];239  TheEdit.InitialCode = Content;240  if (auto Err = TheEdit.Replacements.add(Replacement))241    return Err;242  return llvm::Error::success();243}244 245} // namespace246} // namespace clang::clangd247