[LoongArch][Clang] Make the parameters and return value of {x,}vorn.v builti ns ...
[llvm-project.git] / clang-tools-extra / clangd / refactor / tweaks / ScopifyEnum.cpp
blob44080802a28958ec1f3670019f6da43d69aa8097
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-exception
6 //
7 //===----------------------------------------------------------------------===//
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"
27 #include <cstddef>
28 #include <functional>
29 #include <memory>
30 #include <string>
31 #include <utility>
33 namespace clang::clangd {
34 namespace {
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 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,
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);
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;
79 REGISTER_TWEAK(ScopifyEnum)
81 bool 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();
91 Expected<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())));
98 if (auto Err = addClassKeywordToDeclarations())
99 return std::move(Err);
100 if (auto Err = scopifyEnumValues())
101 return std::move(Err);
103 return E;
106 llvm::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;
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))
119 return Err;
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)) {
129 StripPrefix = false;
130 break;
133 for (const EnumConstantDecl *E : D->enumerators()) {
134 if (auto Err = scopifyEnumValue(*E, EnumName, StripPrefix))
135 return Err;
137 return llvm::Error::success();
140 llvm::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, {});
157 if (auto Err = addReplacementForReference(Ref, MakeReplacement))
158 return Err;
160 continue;
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;
183 return false;
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, "::");
194 return IsAlreadyScoped() ? tooling::Replacement()
195 : tooling::Replacement(FilePath, Offset, 0,
196 EnumName.str() + "::");
198 if (auto Err = addReplacementForReference(Ref, MakeReplacement))
199 return Err;
202 return llvm::Error::success();
205 llvm::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;
217 llvm::Error
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);
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();
235 llvm::Error
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))
241 return Err;
242 return llvm::Error::success();
245 } // namespace
246 } // namespace clang::clangd