clang-tools 19.0.0git
ScopifyEnum.cpp
Go to the documentation of this file.
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 { 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
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 {
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
79REGISTER_TWEAK(ScopifyEnum)
80
81bool 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
91Expected<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
106llvm::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
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)) {
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
140llvm::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
205llvm::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
217llvm::Error
218ScopifyEnum::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
235llvm::Error
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))
241 return Err;
242 return llvm::Error::success();
243}
244
245} // namespace
246} // namespace clang::clangd
const Expr * E
size_t Offset
unsigned Length
#define REGISTER_TWEAK(Subclass)
Definition: Tweak.h:129
Position sourceLocToPosition(const SourceManager &SM, SourceLocation Loc)
Turn a SourceLocation into a [line, column] pair.
Definition: SourceCode.cpp:214
ReferencesResult findReferences(ParsedAST &AST, Position Pos, uint32_t Limit, const SymbolIndex *Index, bool AddContext)
Returns references of the symbol at a specified Pos.
Definition: XRefs.cpp:1375
llvm::Expected< size_t > positionToOffset(llvm::StringRef Code, Position P, bool AllowColumnsBeyondLineLength)
Turn a [line, column] pair into an offset in Code.
Definition: SourceCode.cpp:173
static const llvm::StringLiteral REFACTOR_KIND
Definition: Protocol.h:1071
std::vector< Reference > References
Definition: XRefs.h:94