1 /*
2 * Copyright (c) 2025 Huawei Device Co., Ltd.
3 * Licensed under the Apache License, Version 2.0 (the "License");
4 * you may not use this file except in compliance with the License.
5 * You may obtain a copy of the License at
6 *
7 * http://www.apache.org/licenses/LICENSE-2.0
8 *
9 * Unless required by applicable law or agreed to in writing, software
10 * distributed under the License is distributed on an "AS IS" BASIS,
11 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 * See the License for the specific language governing permissions and
13 * limitations under the License.
14 */
15
16 #include "lsp/include/code_fix_provider.h"
17 #include <cstddef>
18 #include <iostream>
19 #include <memory>
20 #include <string>
21 #include <utility>
22 #include <vector>
23 #include <regex>
24 #include <stdexcept>
25 #include "lsp/include/internal_api.h"
26
27 namespace ark::es2panda::lsp {
28
RegisterCodeFix(const std::string & aliasName,std::unique_ptr<CodeFixRegistration> registration)29 void CodeFixProvider::RegisterCodeFix(const std::string &aliasName, std::unique_ptr<CodeFixRegistration> registration)
30 {
31 (void)aliasName;
32 ASSERT(!aliasName.empty());
33 auto shared = std::shared_ptr<CodeFixRegistration>(std::move(registration));
34
35 for (auto error : shared->GetErrorCodes()) {
36 errorCodeToFixes_.emplace(std::to_string(error), shared);
37 }
38 if (!shared->GetFixIds().empty()) {
39 for (const auto &fixId : shared->GetFixIds()) {
40 fixIdToRegistration_.emplace(fixId, shared);
41 }
42 }
43 }
44
Instance()45 CodeFixProvider &CodeFixProvider::Instance()
46 {
47 static CodeFixProvider instance;
48 return instance;
49 }
50
FormatWithArgs(const std::string & text)51 std::string CodeFixProvider::FormatWithArgs(const std::string &text)
52 {
53 std::string result = text;
54 const std::string regExp = R"(\{(\d+)\})";
55 std::regex pattern(regExp);
56 std::smatch match;
57 return result;
58 }
59
DiagnosticToString(const DiagnosticAndArguments & diag)60 std::string CodeFixProvider::DiagnosticToString(const DiagnosticAndArguments &diag)
61 {
62 std::string message;
63 if (diag.arguments.empty()) {
64 message = diag.message.message;
65 } else {
66 message = FormatWithArgs(diag.message.message);
67 }
68 return message;
69 }
70
CreateCodeFixActionWorker(std::string & fixName,std::string & description,std::vector<FileTextChanges> & changes,std::string & fixId,std::string & fixAllDescription,std::vector<CodeActionCommand> command={})71 CodeFixAction CodeFixProvider::CreateCodeFixActionWorker(std::string &fixName, std::string &description,
72 std::vector<FileTextChanges> &changes, std::string &fixId,
73 std::string &fixAllDescription,
74 std::vector<CodeActionCommand> command = {})
75 {
76 CodeAction codeAction;
77 codeAction.description = description;
78 codeAction.changes = changes;
79 codeAction.commands = std::move(command);
80 return {codeAction, fixName, fixId, fixAllDescription};
81 }
82
CreateCodeFixActionWithoutFixAll(std::string & fixName,std::vector<FileTextChanges> & changes,DiagnosticAndArguments & description)83 CodeFixAction CodeFixProvider::CreateCodeFixActionWithoutFixAll(std::string &fixName,
84 std::vector<FileTextChanges> &changes,
85 DiagnosticAndArguments &description)
86 {
87 std::string fixId;
88 std::string descriptionMessage = DiagnosticToString(description);
89 std::string fixAllDescription;
90 return CreateCodeFixActionWorker(fixName, descriptionMessage, changes, fixId, fixAllDescription);
91 }
92
CreateCodeFixAction(std::string fixName,std::vector<FileTextChanges> changes,DiagnosticAndArguments & description,std::string fixId,DiagnosticAndArguments & fixAllDescription,std::vector<CodeActionCommand> & command)93 CodeFixAction CodeFixProvider::CreateCodeFixAction(std::string fixName, std::vector<FileTextChanges> changes,
94 DiagnosticAndArguments &description, std::string fixId,
95 DiagnosticAndArguments &fixAllDescription,
96 std::vector<CodeActionCommand> &command)
97 {
98 std::string descriptionMessage = DiagnosticToString(description);
99 std::string fixAllDescriptionMessage = DiagnosticToString(fixAllDescription);
100 return CreateCodeFixActionWorker(fixName, descriptionMessage, changes, fixId, fixAllDescriptionMessage,
101 std::move(command));
102 }
103
GetFileName(const std::string & filePath)104 std::string CodeFixProvider::GetFileName(const std::string &filePath)
105 {
106 if (filePath.empty()) {
107 return "";
108 }
109
110 std::size_t pos = filePath.find_last_of('/');
111 if (pos != std::string::npos) {
112 return filePath.substr(pos + 1);
113 }
114
115 pos = filePath.find_last_of('\\');
116 if (pos != std::string::npos) {
117 return filePath.substr(pos + 1);
118 }
119
120 return filePath;
121 }
122
GetSupportedErrorCodes()123 std::vector<std::string> CodeFixProvider::GetSupportedErrorCodes()
124 {
125 std::vector<std::string> result;
126 for (const auto &kv : errorCodeToFixes_) {
127 result.push_back(kv.first);
128 }
129 return result;
130 }
131
GetDiagnostics(const CodeFixContextBase & context)132 DiagnosticReferences *CodeFixProvider::GetDiagnostics(const CodeFixContextBase &context)
133 {
134 DiagnosticReferences *result = nullptr;
135 LSPAPI const *lspApi = GetImpl();
136 Initializer initializer = Initializer();
137 auto it = reinterpret_cast<public_lib::Context *>(context.context);
138 std::string fileNameStr(GetFileName(std::string(it->sourceFile->filePath)));
139 std::string sourceStr(it->sourceFile->source);
140 const auto ctx = initializer.CreateContext(fileNameStr.c_str(), ES2PANDA_STATE_CHECKED, sourceStr.c_str());
141 DiagnosticReferences semantic = lspApi->getSemanticDiagnostics(ctx);
142 DiagnosticReferences syntactic = lspApi->getSyntacticDiagnostics(ctx);
143 DiagnosticReferences suggestions = lspApi->getSuggestionDiagnostics(ctx);
144
145 for (const auto &d : semantic.diagnostic) {
146 result->diagnostic.push_back(d);
147 }
148 for (const auto &d : syntactic.diagnostic) {
149 result->diagnostic.push_back(d);
150 }
151 for (const auto &d : suggestions.diagnostic) {
152 result->diagnostic.push_back(d);
153 }
154 initializer.DestroyContext(ctx);
155 return result;
156 }
157
ShouldIncludeFixAll(const CodeFixRegistration & registration,const std::vector<Diagnostic> & diagnostics)158 bool CodeFixProvider::ShouldIncludeFixAll(const CodeFixRegistration ®istration,
159 const std::vector<Diagnostic> &diagnostics)
160 {
161 int maybeFixableDiagnostics = 0;
162 const int minFixableDiagnostics = 1;
163 for (size_t i = 0; i <= diagnostics.size(); i++) {
164 if (std::find(registration.GetErrorCodes().begin(), registration.GetErrorCodes().end(), i) !=
165 registration.GetErrorCodes().end()) {
166 ++maybeFixableDiagnostics;
167 if (maybeFixableDiagnostics > minFixableDiagnostics) {
168 break;
169 }
170 }
171 }
172 return maybeFixableDiagnostics > minFixableDiagnostics;
173 }
174
GetAllFixes(const CodeFixAllContext & context)175 CombinedCodeActions CodeFixProvider::GetAllFixes(const CodeFixAllContext &context)
176 {
177 auto it = fixIdToRegistration_.find(context.fixId);
178 if (it == fixIdToRegistration_.end() || !it->second) {
179 return CombinedCodeActions();
180 }
181 const std::shared_ptr<CodeFixRegistration> ®istration = it->second;
182 return registration->GetAllCodeActions(context);
183 }
184
EachDiagnostic(const CodeFixAllContext & context,const std::vector<int> & errorCodes,const std::function<void (const DiagnosticWithLocation &)> & cb)185 void CodeFixProvider::EachDiagnostic(const CodeFixAllContext &context, const std::vector<int> &errorCodes,
186 const std::function<void(const DiagnosticWithLocation &)> &cb)
187 {
188 if (errorCodes.empty()) {
189 }
190 if (cb) {
191 }
192 auto diagnostics = GetDiagnostics(context);
193 if (diagnostics != nullptr) {
194 for (size_t i = 0; i <= diagnostics->diagnostic.size(); i++) {
195 }
196 }
197 }
198
GetFixes(const CodeFixContext & context)199 std::vector<CodeFixAction> CodeFixProvider::GetFixes(const CodeFixContext &context)
200 {
201 std::vector<CodeFixAction> result;
202 auto it = errorCodeToFixes_.find(std::to_string(context.errorCode));
203 if (it != errorCodeToFixes_.end()) {
204 const auto ®istrations = it->second;
205 if (registrations) {
206 auto actions = registrations->GetCodeActions(context);
207 for (auto &action : actions) {
208 result.push_back(action);
209 }
210 }
211 }
212 return result;
213 }
214
CodeFixAll(const CodeFixAllContext & context,const std::vector<int> & errorCodes,std::function<void (ChangeTracker &,const DiagnosticWithLocation &)> use)215 CombinedCodeActions CodeFixProvider::CodeFixAll(
216 const CodeFixAllContext &context, const std::vector<int> &errorCodes,
217 std::function<void(ChangeTracker &, const DiagnosticWithLocation &)> use)
218 {
219 std::vector<CodeActionCommand> commands;
220 TextChangesContext textChangesContext {context.host, context.formatContext, context.preferences};
221 auto changes = ChangeTracker::With(textChangesContext, [&](ChangeTracker &tracker) {
222 EachDiagnostic(context, errorCodes, [&](const DiagnosticWithLocation &diag) { use(tracker, diag); });
223 });
224 return {changes, commands};
225 }
226
CreateFileTextChanges(const std::string & fileName,const std::vector<TextChange> & textChanges)227 FileTextChanges CodeFixProvider::CreateFileTextChanges(const std::string &fileName,
228 const std::vector<TextChange> &textChanges)
229 {
230 return {fileName, textChanges};
231 }
232
233 } // namespace ark::es2panda::lsp
234