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