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
33namespace clang::clangd {
34namespace {
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 { EV1, EV2 };
44/// void f() { E e1 = E::EV1; }
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/// TODO: In the above example, we could detect that the values
51/// start with the enum name, and remove that prefix.
52
53class ScopifyEnum : public Tweak {
54 const char *id() const final;
55 std::string title() const override { return "Convert to scoped enum"; }
56 llvm::StringLiteral kind() const override {
57 return CodeAction::REFACTOR_KIND;
58 }
59 bool prepare(const Selection &Inputs) override;
60 Expected<Tweak::Effect> apply(const Selection &Inputs) override;
61
62 using MakeReplacement =
63 std::function<tooling::Replacement(StringRef, StringRef, unsigned)>;
64 llvm::Error addClassKeywordToDeclarations();
65 llvm::Error scopifyEnumValues();
66 llvm::Error scopifyEnumValue(const EnumConstantDecl &CD, StringRef Prefix);
67 llvm::Expected<StringRef> getContentForFile(StringRef FilePath);
68 unsigned getOffsetFromPosition(const Position &Pos, StringRef Content) const;
69 llvm::Error addReplacementForReference(const ReferencesResult::Reference &Ref,
70 const MakeReplacement &GetReplacement);
71 llvm::Error addReplacement(StringRef FilePath, StringRef Content,
72 const tooling::Replacement &Replacement);
73 Position getPosition(const Decl &D) const;
74
75 const EnumDecl *D = nullptr;
76 const Selection *S = nullptr;
77 SourceManager *SM = nullptr;
78 llvm::SmallVector<std::unique_ptr<llvm::MemoryBuffer>> ExtraBuffers;
79 llvm::StringMap<StringRef> ContentPerFile;
80 Effect E;
81};
82
83REGISTER_TWEAK(ScopifyEnum)
84
85bool ScopifyEnum::prepare(const Selection &Inputs) {
86 if (!Inputs.AST->getLangOpts().CPlusPlus11)
87 return false;
88 const SelectionTree::Node *N = Inputs.ASTSelection.commonAncestor();
89 if (!N)
90 return false;
91 D = N->ASTNode.get<EnumDecl>();
92 return D && !D->isScoped() && D->isThisDeclarationADefinition();
93}
94
95Expected<Tweak::Effect> ScopifyEnum::apply(const Selection &Inputs) {
96 S = &Inputs;
97 SM = &S->AST->getSourceManager();
98 E.FormatEdits = false;
99 ContentPerFile.insert(std::make_pair(SM->getFilename(SpellingLoc: D->getLocation()),
100 SM->getBufferData(FID: SM->getMainFileID())));
101
102 if (auto Err = addClassKeywordToDeclarations())
103 return std::move(Err);
104 if (auto Err = scopifyEnumValues())
105 return std::move(Err);
106
107 return E;
108}
109
110llvm::Error ScopifyEnum::addClassKeywordToDeclarations() {
111 for (const auto &Ref :
112 findReferences(*S->AST, getPosition(*D), 0, S->Index, false)
113 .References) {
114 if (!(Ref.Attributes & ReferencesResult::Declaration))
115 continue;
116
117 static const auto MakeReplacement = [](StringRef FilePath,
118 StringRef Content, unsigned Offset) {
119 return tooling::Replacement(FilePath, Offset, 0, "class ");
120 };
121 if (auto Err = addReplacementForReference(Ref, MakeReplacement))
122 return Err;
123 }
124 return llvm::Error::success();
125}
126
127llvm::Error ScopifyEnum::scopifyEnumValues() {
128 std::string PrefixToInsert(D->getName());
129 PrefixToInsert += "::";
130 for (auto E : D->enumerators()) {
131 if (auto Err = scopifyEnumValue(*E, PrefixToInsert))
132 return Err;
133 }
134 return llvm::Error::success();
135}
136
137llvm::Error ScopifyEnum::scopifyEnumValue(const EnumConstantDecl &CD,
138 StringRef Prefix) {
139 for (const auto &Ref :
140 findReferences(*S->AST, getPosition(CD), 0, S->Index, false)
141 .References) {
142 if (Ref.Attributes & ReferencesResult::Declaration)
143 continue;
144
145 const auto MakeReplacement = [&Prefix](StringRef FilePath,
146 StringRef Content, unsigned Offset) {
147 const auto IsAlreadyScoped = [Content, Offset] {
148 if (Offset < 2)
149 return false;
150 unsigned I = Offset;
151 while (--I > 0) {
152 switch (Content[I]) {
153 case ' ':
154 case '\t':
155 case '\n':
156 continue;
157 case ':':
158 if (Content[I - 1] == ':')
159 return true;
160 [[fallthrough]];
161 default:
162 return false;
163 }
164 }
165 return false;
166 };
167 return IsAlreadyScoped()
168 ? tooling::Replacement()
169 : tooling::Replacement(FilePath, Offset, 0, Prefix);
170 };
171 if (auto Err = addReplacementForReference(Ref, MakeReplacement))
172 return Err;
173 }
174
175 return llvm::Error::success();
176}
177
178llvm::Expected<StringRef> ScopifyEnum::getContentForFile(StringRef FilePath) {
179 if (auto It = ContentPerFile.find(Key: FilePath); It != ContentPerFile.end())
180 return It->second;
181 auto Buffer = S->FS->getBufferForFile(Name: FilePath);
182 if (!Buffer)
183 return llvm::errorCodeToError(EC: Buffer.getError());
184 StringRef Content = Buffer->get()->getBuffer();
185 ExtraBuffers.push_back(Elt: std::move(*Buffer));
186 ContentPerFile.insert(KV: std::make_pair(x&: FilePath, y&: Content));
187 return Content;
188}
189
190unsigned int ScopifyEnum::getOffsetFromPosition(const Position &Pos,
191 StringRef Content) const {
192 unsigned int Offset = 0;
193
194 for (std::size_t LinesRemaining = Pos.line;
195 Offset < Content.size() && LinesRemaining;) {
196 if (Content[Offset++] == '\n')
197 --LinesRemaining;
198 }
199 return Offset + Pos.character;
200}
201
202llvm::Error
203ScopifyEnum::addReplacementForReference(const ReferencesResult::Reference &Ref,
204 const MakeReplacement &GetReplacement) {
205 StringRef FilePath = Ref.Loc.uri.file();
206 auto Content = getContentForFile(FilePath);
207 if (!Content)
208 return Content.takeError();
209 unsigned Offset = getOffsetFromPosition(Pos: Ref.Loc.range.start, Content: *Content);
210 tooling::Replacement Replacement = GetReplacement(FilePath, *Content, Offset);
211 if (Replacement.isApplicable())
212 return addReplacement(FilePath, Content: *Content, Replacement);
213 return llvm::Error::success();
214}
215
216llvm::Error
217ScopifyEnum::addReplacement(StringRef FilePath, StringRef Content,
218 const tooling::Replacement &Replacement) {
219 Edit &TheEdit = E.ApplyEdits[FilePath];
220 TheEdit.InitialCode = Content;
221 if (auto Err = TheEdit.Replacements.add(R: Replacement))
222 return Err;
223 return llvm::Error::success();
224}
225
226Position ScopifyEnum::getPosition(const Decl &D) const {
227 const SourceLocation Loc = D.getLocation();
228 Position Pos;
229 Pos.line = SM->getSpellingLineNumber(Loc) - 1;
230 Pos.character = SM->getSpellingColumnNumber(Loc) - 1;
231 return Pos;
232}
233
234} // namespace
235} // namespace clang::clangd
236

source code of clang-tools-extra/clangd/refactor/tweaks/ScopifyEnum.cpp