1 //===--- ScopifyEnum.cpp --------------------------------------- -*- C++-*-===//
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-exception
7 //===----------------------------------------------------------------------===//
11 #include "Selection.h"
12 #include "SourceCode.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"
33 namespace clang::clangd
{
36 /// Turns an unscoped into a scoped enum type.
38 /// enum E { EV1, EV2 };
40 /// void f() { E e1 = EV1; }
43 /// enum class E { V1, V2 };
44 /// void f() { E e1 = E::V1; }
46 /// Note that the respective project code might not compile anymore
47 /// if it made use of the now-gone implicit conversion to int.
48 /// This is out of scope for this tweak.
50 class 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
;
56 bool prepare(const Selection
&Inputs
) override
;
57 Expected
<Tweak::Effect
> apply(const Selection
&Inputs
) override
;
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
,
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
);
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
;
79 REGISTER_TWEAK(ScopifyEnum
)
81 bool ScopifyEnum::prepare(const Selection
&Inputs
) {
82 if (!Inputs
.AST
->getLangOpts().CPlusPlus11
)
84 const SelectionTree::Node
*N
= Inputs
.ASTSelection
.commonAncestor();
87 D
= N
->ASTNode
.get
<EnumDecl
>();
88 return D
&& !D
->isScoped() && D
->isThisDeclarationADefinition();
91 Expected
<Tweak::Effect
> ScopifyEnum::apply(const Selection
&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())));
98 if (auto Err
= addClassKeywordToDeclarations())
99 return std::move(Err
);
100 if (auto Err
= scopifyEnumValues())
101 return std::move(Err
);
106 llvm::Error
ScopifyEnum::addClassKeywordToDeclarations() {
107 for (const auto &Ref
:
108 findReferences(*S
->AST
, sourceLocToPosition(*SM
, D
->getBeginLoc()), 0,
111 if (!(Ref
.Attributes
& ReferencesResult::Declaration
))
114 static const auto MakeReplacement
= [](StringRef FilePath
,
115 StringRef Content
, unsigned Offset
) {
116 return tooling::Replacement(FilePath
, Offset
, 0, "class ");
118 if (auto Err
= addReplacementForReference(Ref
, MakeReplacement
))
121 return llvm::Error::success();
124 llvm::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
)) {
133 for (const EnumConstantDecl
*E
: D
->enumerators()) {
134 if (auto Err
= scopifyEnumValue(*E
, EnumName
, StripPrefix
))
137 return llvm::Error::success();
140 llvm::Error
ScopifyEnum::scopifyEnumValue(const EnumConstantDecl
&CD
,
143 for (const auto &Ref
:
144 findReferences(*S
->AST
, sourceLocToPosition(*SM
, CD
.getBeginLoc()), 0,
147 if (Ref
.Attributes
& ReferencesResult::Declaration
) {
149 const auto MakeReplacement
= [&EnumName
](StringRef FilePath
,
152 unsigned Length
= EnumName
.size();
153 if (Content
[Offset
+ Length
] == '_')
155 return tooling::Replacement(FilePath
, Offset
, Length
, {});
157 if (auto Err
= addReplacementForReference(Ref
, MakeReplacement
))
163 const auto MakeReplacement
= [&](StringRef FilePath
, StringRef Content
,
165 const auto IsAlreadyScoped
= [Content
, Offset
] {
170 switch (Content
[I
]) {
176 if (Content
[I
- 1] == ':')
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(),
194 return IsAlreadyScoped() ? tooling::Replacement()
195 : tooling::Replacement(FilePath
, Offset
, 0,
196 EnumName
.str() + "::");
198 if (auto Err
= addReplacementForReference(Ref
, MakeReplacement
))
202 return llvm::Error::success();
205 llvm::Expected
<StringRef
> ScopifyEnum::getContentForFile(StringRef FilePath
) {
206 if (auto It
= ContentPerFile
.find(FilePath
); It
!= ContentPerFile
.end())
208 auto Buffer
= S
->FS
->getBufferForFile(FilePath
);
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
));
218 ScopifyEnum::addReplacementForReference(const ReferencesResult::Reference
&Ref
,
219 const MakeReplacement
&GetReplacement
) {
220 StringRef FilePath
= Ref
.Loc
.uri
.file();
221 llvm::Expected
<StringRef
> Content
= getContentForFile(FilePath
);
223 return Content
.takeError();
224 llvm::Expected
<size_t> Offset
=
225 positionToOffset(*Content
, Ref
.Loc
.range
.start
);
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();
236 ScopifyEnum::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
))
242 return llvm::Error::success();
246 } // namespace clang::clangd