10255b217Sckandeler //===--- ScopifyEnum.cpp --------------------------------------- -*- C++-*-===//
20255b217Sckandeler //
30255b217Sckandeler // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
40255b217Sckandeler // See https://llvm.org/LICENSE.txt for license information.
50255b217Sckandeler // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
60255b217Sckandeler //
70255b217Sckandeler //===----------------------------------------------------------------------===//
80255b217Sckandeler
90255b217Sckandeler #include "ParsedAST.h"
10ec095b74SKadir Cetinkaya #include "Protocol.h"
11ec095b74SKadir Cetinkaya #include "Selection.h"
12ec095b74SKadir Cetinkaya #include "SourceCode.h"
130255b217Sckandeler #include "XRefs.h"
140255b217Sckandeler #include "refactor/Tweak.h"
15ec095b74SKadir Cetinkaya #include "clang/AST/Decl.h"
16ec095b74SKadir Cetinkaya #include "clang/AST/DeclBase.h"
17ec095b74SKadir Cetinkaya #include "clang/Basic/LLVM.h"
180255b217Sckandeler #include "clang/Basic/SourceLocation.h"
190255b217Sckandeler #include "clang/Basic/SourceManager.h"
200255b217Sckandeler #include "clang/Tooling/Core/Replacement.h"
21ec095b74SKadir Cetinkaya #include "llvm/ADT/SmallVector.h"
22ec095b74SKadir Cetinkaya #include "llvm/ADT/StringMap.h"
230255b217Sckandeler #include "llvm/ADT/StringRef.h"
240255b217Sckandeler #include "llvm/Support/Error.h"
25ec095b74SKadir Cetinkaya #include "llvm/Support/MemoryBuffer.h"
260255b217Sckandeler
27ec095b74SKadir Cetinkaya #include <cstddef>
280255b217Sckandeler #include <functional>
29ec095b74SKadir Cetinkaya #include <memory>
30ec095b74SKadir Cetinkaya #include <string>
31ec095b74SKadir Cetinkaya #include <utility>
320255b217Sckandeler
330255b217Sckandeler namespace clang::clangd {
340255b217Sckandeler namespace {
350255b217Sckandeler
360255b217Sckandeler /// Turns an unscoped into a scoped enum type.
370255b217Sckandeler /// Before:
380255b217Sckandeler /// enum E { EV1, EV2 };
390255b217Sckandeler /// ^
400255b217Sckandeler /// void f() { E e1 = EV1; }
410255b217Sckandeler ///
420255b217Sckandeler /// After:
43f2daa32fSChristian Kandeler /// enum class E { V1, V2 };
44f2daa32fSChristian Kandeler /// void f() { E e1 = E::V1; }
450255b217Sckandeler ///
460255b217Sckandeler /// Note that the respective project code might not compile anymore
470255b217Sckandeler /// if it made use of the now-gone implicit conversion to int.
480255b217Sckandeler /// This is out of scope for this tweak.
490255b217Sckandeler
500255b217Sckandeler class ScopifyEnum : public Tweak {
510255b217Sckandeler const char *id() const final;
title() const520255b217Sckandeler std::string title() const override { return "Convert to scoped enum"; }
kind() const530255b217Sckandeler llvm::StringLiteral kind() const override {
540255b217Sckandeler return CodeAction::REFACTOR_KIND;
550255b217Sckandeler }
560255b217Sckandeler bool prepare(const Selection &Inputs) override;
570255b217Sckandeler Expected<Tweak::Effect> apply(const Selection &Inputs) override;
580255b217Sckandeler
590255b217Sckandeler using MakeReplacement =
600255b217Sckandeler std::function<tooling::Replacement(StringRef, StringRef, unsigned)>;
610255b217Sckandeler llvm::Error addClassKeywordToDeclarations();
620255b217Sckandeler llvm::Error scopifyEnumValues();
63f2daa32fSChristian Kandeler llvm::Error scopifyEnumValue(const EnumConstantDecl &CD, StringRef EnumName,
64f2daa32fSChristian Kandeler bool StripPrefix);
650255b217Sckandeler llvm::Expected<StringRef> getContentForFile(StringRef FilePath);
660255b217Sckandeler llvm::Error addReplacementForReference(const ReferencesResult::Reference &Ref,
670255b217Sckandeler const MakeReplacement &GetReplacement);
680255b217Sckandeler llvm::Error addReplacement(StringRef FilePath, StringRef Content,
690255b217Sckandeler const tooling::Replacement &Replacement);
700255b217Sckandeler
710255b217Sckandeler const EnumDecl *D = nullptr;
720255b217Sckandeler const Selection *S = nullptr;
730255b217Sckandeler SourceManager *SM = nullptr;
740255b217Sckandeler llvm::SmallVector<std::unique_ptr<llvm::MemoryBuffer>> ExtraBuffers;
750255b217Sckandeler llvm::StringMap<StringRef> ContentPerFile;
760255b217Sckandeler Effect E;
770255b217Sckandeler };
780255b217Sckandeler
REGISTER_TWEAK(ScopifyEnum)790255b217Sckandeler REGISTER_TWEAK(ScopifyEnum)
800255b217Sckandeler
810255b217Sckandeler bool ScopifyEnum::prepare(const Selection &Inputs) {
820255b217Sckandeler if (!Inputs.AST->getLangOpts().CPlusPlus11)
830255b217Sckandeler return false;
840255b217Sckandeler const SelectionTree::Node *N = Inputs.ASTSelection.commonAncestor();
850255b217Sckandeler if (!N)
860255b217Sckandeler return false;
870255b217Sckandeler D = N->ASTNode.get<EnumDecl>();
880255b217Sckandeler return D && !D->isScoped() && D->isThisDeclarationADefinition();
890255b217Sckandeler }
900255b217Sckandeler
apply(const Selection & Inputs)910255b217Sckandeler Expected<Tweak::Effect> ScopifyEnum::apply(const Selection &Inputs) {
920255b217Sckandeler S = &Inputs;
930255b217Sckandeler SM = &S->AST->getSourceManager();
940255b217Sckandeler E.FormatEdits = false;
950255b217Sckandeler ContentPerFile.insert(std::make_pair(SM->getFilename(D->getLocation()),
960255b217Sckandeler SM->getBufferData(SM->getMainFileID())));
970255b217Sckandeler
980255b217Sckandeler if (auto Err = addClassKeywordToDeclarations())
99ec095b74SKadir Cetinkaya return std::move(Err);
1000255b217Sckandeler if (auto Err = scopifyEnumValues())
101ec095b74SKadir Cetinkaya return std::move(Err);
1020255b217Sckandeler
1030255b217Sckandeler return E;
1040255b217Sckandeler }
1050255b217Sckandeler
addClassKeywordToDeclarations()1060255b217Sckandeler llvm::Error ScopifyEnum::addClassKeywordToDeclarations() {
1070255b217Sckandeler for (const auto &Ref :
108*8d946c71SJulian Schmidt findReferences(*S->AST, sourceLocToPosition(*SM, D->getBeginLoc()), 0,
109*8d946c71SJulian Schmidt S->Index, false)
1100255b217Sckandeler .References) {
1110255b217Sckandeler if (!(Ref.Attributes & ReferencesResult::Declaration))
1120255b217Sckandeler continue;
1130255b217Sckandeler
1140255b217Sckandeler static const auto MakeReplacement = [](StringRef FilePath,
1150255b217Sckandeler StringRef Content, unsigned Offset) {
1160255b217Sckandeler return tooling::Replacement(FilePath, Offset, 0, "class ");
1170255b217Sckandeler };
1180255b217Sckandeler if (auto Err = addReplacementForReference(Ref, MakeReplacement))
1190255b217Sckandeler return Err;
1200255b217Sckandeler }
1210255b217Sckandeler return llvm::Error::success();
1220255b217Sckandeler }
1230255b217Sckandeler
scopifyEnumValues()1240255b217Sckandeler llvm::Error ScopifyEnum::scopifyEnumValues() {
125f2daa32fSChristian Kandeler StringRef EnumName(D->getName());
126f2daa32fSChristian Kandeler bool StripPrefix = true;
127f2daa32fSChristian Kandeler for (const EnumConstantDecl *E : D->enumerators()) {
128f2daa32fSChristian Kandeler if (!E->getName().starts_with(EnumName)) {
129f2daa32fSChristian Kandeler StripPrefix = false;
130f2daa32fSChristian Kandeler break;
131f2daa32fSChristian Kandeler }
132f2daa32fSChristian Kandeler }
133f2daa32fSChristian Kandeler for (const EnumConstantDecl *E : D->enumerators()) {
134f2daa32fSChristian Kandeler if (auto Err = scopifyEnumValue(*E, EnumName, StripPrefix))
1350255b217Sckandeler return Err;
1360255b217Sckandeler }
1370255b217Sckandeler return llvm::Error::success();
1380255b217Sckandeler }
1390255b217Sckandeler
scopifyEnumValue(const EnumConstantDecl & CD,StringRef EnumName,bool StripPrefix)1400255b217Sckandeler llvm::Error ScopifyEnum::scopifyEnumValue(const EnumConstantDecl &CD,
141f2daa32fSChristian Kandeler StringRef EnumName,
142f2daa32fSChristian Kandeler bool StripPrefix) {
1430255b217Sckandeler for (const auto &Ref :
144*8d946c71SJulian Schmidt findReferences(*S->AST, sourceLocToPosition(*SM, CD.getBeginLoc()), 0,
145*8d946c71SJulian Schmidt S->Index, false)
1460255b217Sckandeler .References) {
147f2daa32fSChristian Kandeler if (Ref.Attributes & ReferencesResult::Declaration) {
148f2daa32fSChristian Kandeler if (StripPrefix) {
149f2daa32fSChristian Kandeler const auto MakeReplacement = [&EnumName](StringRef FilePath,
150f2daa32fSChristian Kandeler StringRef Content,
151f2daa32fSChristian Kandeler unsigned Offset) {
152f2daa32fSChristian Kandeler unsigned Length = EnumName.size();
153f2daa32fSChristian Kandeler if (Content[Offset + Length] == '_')
154f2daa32fSChristian Kandeler ++Length;
155f2daa32fSChristian Kandeler return tooling::Replacement(FilePath, Offset, Length, {});
156f2daa32fSChristian Kandeler };
157f2daa32fSChristian Kandeler if (auto Err = addReplacementForReference(Ref, MakeReplacement))
158f2daa32fSChristian Kandeler return Err;
159f2daa32fSChristian Kandeler }
1600255b217Sckandeler continue;
161f2daa32fSChristian Kandeler }
1620255b217Sckandeler
163f2daa32fSChristian Kandeler const auto MakeReplacement = [&](StringRef FilePath, StringRef Content,
164f2daa32fSChristian Kandeler unsigned Offset) {
1650255b217Sckandeler const auto IsAlreadyScoped = [Content, Offset] {
1660255b217Sckandeler if (Offset < 2)
1670255b217Sckandeler return false;
1680255b217Sckandeler unsigned I = Offset;
1690255b217Sckandeler while (--I > 0) {
1700255b217Sckandeler switch (Content[I]) {
1710255b217Sckandeler case ' ':
1720255b217Sckandeler case '\t':
1730255b217Sckandeler case '\n':
1740255b217Sckandeler continue;
1750255b217Sckandeler case ':':
1760255b217Sckandeler if (Content[I - 1] == ':')
1770255b217Sckandeler return true;
1780255b217Sckandeler [[fallthrough]];
1790255b217Sckandeler default:
1800255b217Sckandeler return false;
1810255b217Sckandeler }
1820255b217Sckandeler }
1830255b217Sckandeler return false;
1840255b217Sckandeler };
185f2daa32fSChristian Kandeler if (StripPrefix) {
186f2daa32fSChristian Kandeler const int ExtraLength =
187f2daa32fSChristian Kandeler Content[Offset + EnumName.size()] == '_' ? 1 : 0;
188f2daa32fSChristian Kandeler if (IsAlreadyScoped())
189f2daa32fSChristian Kandeler return tooling::Replacement(FilePath, Offset,
190f2daa32fSChristian Kandeler EnumName.size() + ExtraLength, {});
191f2daa32fSChristian Kandeler return tooling::Replacement(FilePath, Offset + EnumName.size(),
192f2daa32fSChristian Kandeler ExtraLength, "::");
193f2daa32fSChristian Kandeler }
194f2daa32fSChristian Kandeler return IsAlreadyScoped() ? tooling::Replacement()
195f2daa32fSChristian Kandeler : tooling::Replacement(FilePath, Offset, 0,
196f2daa32fSChristian Kandeler EnumName.str() + "::");
1970255b217Sckandeler };
1980255b217Sckandeler if (auto Err = addReplacementForReference(Ref, MakeReplacement))
1990255b217Sckandeler return Err;
2000255b217Sckandeler }
2010255b217Sckandeler
2020255b217Sckandeler return llvm::Error::success();
2030255b217Sckandeler }
2040255b217Sckandeler
getContentForFile(StringRef FilePath)2050255b217Sckandeler llvm::Expected<StringRef> ScopifyEnum::getContentForFile(StringRef FilePath) {
2060255b217Sckandeler if (auto It = ContentPerFile.find(FilePath); It != ContentPerFile.end())
2070255b217Sckandeler return It->second;
2080255b217Sckandeler auto Buffer = S->FS->getBufferForFile(FilePath);
2090255b217Sckandeler if (!Buffer)
2100255b217Sckandeler return llvm::errorCodeToError(Buffer.getError());
2110255b217Sckandeler StringRef Content = Buffer->get()->getBuffer();
2120255b217Sckandeler ExtraBuffers.push_back(std::move(*Buffer));
2130255b217Sckandeler ContentPerFile.insert(std::make_pair(FilePath, Content));
2140255b217Sckandeler return Content;
2150255b217Sckandeler }
2160255b217Sckandeler
2170255b217Sckandeler llvm::Error
addReplacementForReference(const ReferencesResult::Reference & Ref,const MakeReplacement & GetReplacement)2180255b217Sckandeler ScopifyEnum::addReplacementForReference(const ReferencesResult::Reference &Ref,
2190255b217Sckandeler const MakeReplacement &GetReplacement) {
2200255b217Sckandeler StringRef FilePath = Ref.Loc.uri.file();
221*8d946c71SJulian Schmidt llvm::Expected<StringRef> Content = getContentForFile(FilePath);
2220255b217Sckandeler if (!Content)
2230255b217Sckandeler return Content.takeError();
224*8d946c71SJulian Schmidt llvm::Expected<size_t> Offset =
225*8d946c71SJulian Schmidt positionToOffset(*Content, Ref.Loc.range.start);
226*8d946c71SJulian Schmidt if (!Offset)
227*8d946c71SJulian Schmidt return Offset.takeError();
228*8d946c71SJulian Schmidt tooling::Replacement Replacement =
229*8d946c71SJulian Schmidt GetReplacement(FilePath, *Content, *Offset);
2300255b217Sckandeler if (Replacement.isApplicable())
2310255b217Sckandeler return addReplacement(FilePath, *Content, Replacement);
2320255b217Sckandeler return llvm::Error::success();
2330255b217Sckandeler }
2340255b217Sckandeler
2350255b217Sckandeler llvm::Error
addReplacement(StringRef FilePath,StringRef Content,const tooling::Replacement & Replacement)2360255b217Sckandeler ScopifyEnum::addReplacement(StringRef FilePath, StringRef Content,
2370255b217Sckandeler const tooling::Replacement &Replacement) {
2380255b217Sckandeler Edit &TheEdit = E.ApplyEdits[FilePath];
2390255b217Sckandeler TheEdit.InitialCode = Content;
2400255b217Sckandeler if (auto Err = TheEdit.Replacements.add(Replacement))
2410255b217Sckandeler return Err;
2420255b217Sckandeler return llvm::Error::success();
2430255b217Sckandeler }
2440255b217Sckandeler
2450255b217Sckandeler } // namespace
2460255b217Sckandeler } // namespace clang::clangd
247