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 //===----------------------------------------------------------------------===//
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
33 namespace clang::clangd {
34 namespace {
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 anymore
47 /// if it made use of the now-gone implicit conversion to int.
48 /// This is out of scope for this tweak.
49
50 class ScopifyEnum : public Tweak {
51 const char *id() const final;
title() const52 std::string title() const override { return "Convert to scoped enum"; }
kind() const53 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
REGISTER_TWEAK(ScopifyEnum)79 REGISTER_TWEAK(ScopifyEnum)
80
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();
89 }
90
apply(const Selection & Inputs)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())));
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
addClassKeywordToDeclarations()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;
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
scopifyEnumValues()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;
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
scopifyEnumValue(const EnumConstantDecl & CD,StringRef EnumName,bool StripPrefix)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, {});
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
getContentForFile(StringRef FilePath)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;
215 }
216
217 llvm::Error
addReplacementForReference(const ReferencesResult::Reference & Ref,const MakeReplacement & GetReplacement)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();
233 }
234
235 llvm::Error
addReplacement(StringRef FilePath,StringRef Content,const tooling::Replacement & Replacement)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();
243 }
244
245 } // namespace
246 } // namespace clang::clangd
247