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"
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 {
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;
81bool 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();
91Expected<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);
106llvm::Error ScopifyEnum::addClassKeywordToDeclarations() {
107 for (
const auto &Ref :
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();
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)) {
133 for (
const EnumConstantDecl *
E : D->enumerators()) {
134 if (
auto Err = scopifyEnumValue(*
E, EnumName, StripPrefix))
137 return llvm::Error::success();
140llvm::Error ScopifyEnum::scopifyEnumValue(
const EnumConstantDecl &CD,
143 for (
const auto &Ref :
149 const auto MakeReplacement = [&EnumName](StringRef FilePath,
152 unsigned Length = EnumName.size();
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();
205llvm::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));
218ScopifyEnum::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 =
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();
236ScopifyEnum::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();
#define REGISTER_TWEAK(Subclass)
FIXME: Skip testing on windows temporarily due to the different escaping code mode.
Position sourceLocToPosition(const SourceManager &SM, SourceLocation Loc)
Turn a SourceLocation into a [line, column] pair.
ReferencesResult findReferences(ParsedAST &AST, Position Pos, uint32_t Limit, const SymbolIndex *Index, bool AddContext)
Returns references of the symbol at a specified Pos.
llvm::Expected< size_t > positionToOffset(llvm::StringRef Code, Position P, bool AllowColumnsBeyondLineLength)
Turn a [line, column] pair into an offset in Code.
static const llvm::StringLiteral REFACTOR_KIND
std::vector< Reference > References