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/services/text_change/change_tracker.h"
17 #include <cstddef>
18 #include <iostream>
19 #include <string>
20 #include <utility>
21 #include <vector>
22 #include "get_adjusted_location.h"
23
24 namespace ark::es2panda::lsp {
25
26 ConfigurableStartEnd g_useNonAdjustedPositions = {{LeadingTriviaOption::EXCLUDE}, {TrailingTriviaOption::EXCLUDE}};
27
FromContext(TextChangesContext & context)28 ChangeTracker ChangeTracker::FromContext(TextChangesContext &context)
29 {
30 return ChangeTracker(context.formatContext, context.formatContext.GetFormatCodeSettings().GetNewLineCharacter());
31 }
32
With(TextChangesContext & context,const std::function<void (ChangeTracker &)> & cb)33 std::vector<FileTextChanges> ChangeTracker::With(TextChangesContext &context,
34 const std::function<void(ChangeTracker &)> &cb)
35 {
36 auto tracker = FromContext(context);
37 cb(tracker);
38 ValidateNonFormattedText validateNonFormattedText = [](ark::es2panda::ir::AstNode *, const std::string &) {};
39 return tracker.GetChanges();
40 }
GetAstFromContext(const es2panda_Context * context)41 ir::AstNode *ChangeTracker::GetAstFromContext(const es2panda_Context *context)
42 {
43 auto ctx = reinterpret_cast<ark::es2panda::public_lib::Context *>(const_cast<es2panda_Context *>(context));
44 auto ast = reinterpret_cast<ir::AstNode *>(ctx->parserProgram->Ast());
45 return ast;
46 }
47
GetStartPositionOfLine(size_t line,const es2panda_Context * context)48 size_t ChangeTracker::GetStartPositionOfLine(size_t line, const es2panda_Context *context)
49 {
50 auto ast = GetAstFromContext(context);
51 ir::AstNode *targetNode;
52 ast->FindChild([line, &targetNode](ark::es2panda::ir::AstNode *node) {
53 if (node->Start().line == line) {
54 targetNode = node;
55 }
56 return false;
57 });
58 if (targetNode != nullptr) {
59 return targetNode->Start().index;
60 }
61 return 0;
62 }
RangeContainsPosition(TextRange r,size_t pos)63 bool ChangeTracker::RangeContainsPosition(TextRange r, size_t pos)
64 {
65 return r.pos <= pos && pos <= r.end;
66 }
67
ReplaceRangeWithNodes(es2panda_Context * context,const TextRange range,std::vector<ir::AstNode * > & newNodes,ReplaceWithMultipleNodesOptions options)68 void ChangeTracker::ReplaceRangeWithNodes(es2panda_Context *context, const TextRange range,
69 std::vector<ir::AstNode *> &newNodes, ReplaceWithMultipleNodesOptions options)
70 {
71 auto astContext = reinterpret_cast<ark::es2panda::public_lib::Context *>(context);
72 const auto sourceFile = astContext->sourceFile;
73 ReplaceWithMultipleNodes replaceNodes = {sourceFile, range, ChangeKind::REPLACEWITHMULTIPLENODES, newNodes,
74 options};
75 changes_.emplace_back(replaceNodes);
76 }
NextCommaToken(es2panda_Context * context,const ir::AstNode * node)77 ir::AstNode *ChangeTracker::NextCommaToken(es2panda_Context *context, const ir::AstNode *node)
78 {
79 auto astContext = reinterpret_cast<ark::es2panda::public_lib::Context *>(context);
80 auto *astNodes = astContext->parserProgram->Ast();
81 const auto children = GetChildren(astNodes, astContext->allocator);
82 const auto next = FindRightToken(node->Start().index, children);
83 return next;
84 }
85
InsertNodesAt(es2panda_Context * context,const size_t pos,std::vector<ir::AstNode * > & newNodes,ReplaceWithMultipleNodesOptions options)86 void ChangeTracker::InsertNodesAt(es2panda_Context *context, const size_t pos, std::vector<ir::AstNode *> &newNodes,
87 ReplaceWithMultipleNodesOptions options)
88 {
89 const auto posRange = CreateRange(pos);
90 ReplaceRangeWithNodes(context, posRange, newNodes, std::move(options));
91 }
92
InsertAtTopOfFile(es2panda_Context * context,const std::variant<ir::AstNode *,std::vector<ir::AstNode * >> & insert,bool blankLineBetween)93 void ChangeTracker::InsertAtTopOfFile(es2panda_Context *context,
94 const std::variant<ir::AstNode *, std::vector<ir::AstNode *>> &insert,
95 bool blankLineBetween)
96 {
97 auto astContext = reinterpret_cast<ark::es2panda::public_lib::Context *>(const_cast<es2panda_Context *>(context));
98 const auto sourceFile = astContext->sourceFile;
99 const auto sourceFileAst = GetAstFromContext(context);
100 const size_t pos = GetInsertionPositionAtSourceFileTop(sourceFileAst);
101
102 std::string prefix = (pos == 0) ? "" : "\n";
103 char currentChar = pos < sourceFile->source.size() ? sourceFile->source.at(pos) : '\0';
104 std::string suffix = (IsLineBreak(currentChar) ? "" : "\n");
105 if (blankLineBetween) {
106 suffix += "\n";
107 }
108 if (std::holds_alternative<std::vector<ir::AstNode *>>(insert)) {
109 ReplaceWithMultipleNodesOptions options;
110 options.suffix = suffix;
111 options.prefix = prefix;
112 auto list = std::get<std::vector<ir::AstNode *>>(insert);
113 InsertNodesAt(context, pos, list, options);
114 } else {
115 InsertNodeOptions options;
116 options.suffix = suffix;
117 options.prefix = prefix;
118 InsertNodeAt(context, pos, std::get<ir::AstNode *>(insert), options);
119 }
120 }
121
GetOptionsForInsertNodeBefore(const ir::AstNode * before,const ir::AstNode * inserted,const bool blankLineBetween)122 InsertNodeOptions ChangeTracker::GetOptionsForInsertNodeBefore(const ir::AstNode *before, const ir::AstNode *inserted,
123 const bool blankLineBetween)
124 {
125 InsertNodeOptions options;
126 if (before->IsStatement() || before->IsClassProperty()) {
127 options.suffix = blankLineBetween ? "\n\n" : "\n";
128 } else if (before->IsVariableDeclaration()) {
129 options.suffix = ", ";
130 } else if (before->IsTSTypeParameterDeclaration()) {
131 options.suffix = (inserted->IsTSTypeParameterDeclaration() ? ", " : "");
132 } else if ((before->IsStringLiteral() && before->Parent()->IsImportDeclaration()) || before->IsNamedType()) {
133 options.suffix = ", ";
134 } else if (before->IsImportSpecifier()) {
135 options.suffix = "," + std::string(blankLineBetween ? "\n" : " ");
136 }
137 return options; // We haven't handled this kind of node yet -- add it
138 }
139
GetMembersOrProperties(const ir::AstNode * node)140 std::vector<ir::AstNode *> ChangeTracker::GetMembersOrProperties(const ir::AstNode *node)
141 {
142 std::vector<ir::AstNode *> membersOrProperties;
143 if (node->IsObjectExpression()) {
144 const auto &properties = node->AsObjectExpression()->Properties();
145 membersOrProperties.reserve(properties.size());
146 for (auto *property : properties) {
147 membersOrProperties.emplace_back(property->AsExpression());
148 }
149 } else {
150 node->FindChild([&membersOrProperties](ir::AstNode *n) {
151 if (n->IsMemberExpression() || n->IsTSEnumMember()) {
152 membersOrProperties.emplace_back(n);
153 }
154 return false;
155 });
156 }
157 return membersOrProperties;
158 }
159
GetInsertNodeAtStartInsertOptions(const ir::AstNode * node)160 InsertNodeOptions ChangeTracker::GetInsertNodeAtStartInsertOptions(const ir::AstNode *node)
161 {
162 // Rules:
163 // - Always insert leading newline.
164 // - For object literals:
165 // - Add a trailing comma if there are existing members in the node, or the source file is not a JSON file
166 // (because trailing commas are generally illegal in a JSON file).
167 // - Add a leading comma if the source file is not a JSON file, there are existing insertions,
168 // and the node is empty (because we didn't add a trailing comma per the previous rule).
169 // - Only insert a trailing newline if body is single-line and there are no other insertions for the node.
170 // NOTE: This is handled in `finishClassesWithNodesInsertedAtStart`.
171
172 const auto members = GetMembersOrProperties(node);
173 const auto isEmpty = members.empty();
174 const auto isFirstInsertion = classesWithNodesInsertedAtStart_.at(0).node == node;
175 const auto insertTrailingComma = node->IsObjectExpression();
176 const auto insertLeadingComma = node->IsObjectExpression() && isEmpty && !isFirstInsertion;
177 InsertNodeOptions options;
178 options.prefix = (insertLeadingComma ? "," : "") + std::string("\n");
179 options.suffix = insertTrailingComma ? "," : (node->IsTSInterfaceDeclaration() && isEmpty ? ";" : "");
180 options.delta = 0;
181 return {options};
182 }
183
InsertNodeAtStartWorker(es2panda_Context * context,const ir::AstNode * node,const ir::AstNode * newElement)184 void ChangeTracker::InsertNodeAtStartWorker(es2panda_Context *context, const ir::AstNode *node,
185 const ir::AstNode *newElement)
186 {
187 if (node == nullptr || newElement == nullptr) {
188 return;
189 }
190 if (node->IsClassDeclaration() || node->IsTSInterfaceDeclaration() || node->IsTSTypeLiteral() ||
191 node->IsObjectExpression()) {
192 if (newElement->IsClassProperty() || newElement->IsSpreadElement() || newElement->IsMethodDefinition() ||
193 newElement->IsTSPropertySignature()) {
194 const auto membersOrProperties = GetMembersOrProperties(node);
195 const auto size = membersOrProperties.size();
196 InsertNodeOptions options = GetInsertNodeAtStartInsertOptions(node);
197 InsertNodeAt(context, membersOrProperties.at(size - 1)->End().index, newElement, options);
198 }
199 }
200 }
201
NeedSemicolonBetween(const ir::AstNode * a,const ir::AstNode * b)202 bool ChangeTracker::NeedSemicolonBetween(const ir::AstNode *a, const ir::AstNode *b)
203 {
204 if (a == nullptr || b == nullptr) {
205 return false;
206 }
207 return (a->IsTSPropertySignature() || a->IsTSParameterProperty()) && (b->IsClassProperty() || b->IsTyped()) &&
208 (a->IsStatement() || !a->IsDeclare()) && (b->IsStatement() || !b->IsDeclare());
209 }
210
InsertNodeAfterWorker(es2panda_Context * context,ir::AstNode * after,const ir::AstNode * newNode)211 size_t ChangeTracker::InsertNodeAfterWorker(es2panda_Context *context, ir::AstNode *after, const ir::AstNode *newNode)
212 {
213 if (NeedSemicolonBetween(after, newNode)) {
214 // check if previous statement ends with semicolon
215 // if not - insert semicolon to preserve the code from changing the meaning
216 // due to ASI
217 auto astContext =
218 reinterpret_cast<ark::es2panda::public_lib::Context *>(const_cast<es2panda_Context *>(context));
219 const auto sourceFile = astContext->sourceFile;
220 if (sourceFile->source.at(after->End().index - 1) != ':') {
221 InsertNodeOptions options;
222 ReplaceRange(context, CreateRange(after->End().index), nullptr,
223 options); // newNode should get from factory.createToken(':')
224 }
225 }
226
227 auto *ctx = reinterpret_cast<public_lib::Context *>(context);
228
229 const auto endPosition = GetAdjustedLocation(after, false, ctx->allocator);
230 return (*endPosition)->End().index;
231 }
232
GetInsertNodeAfterOptionsWorker(const ir::AstNode * node)233 InsertNodeOptions ChangeTracker::GetInsertNodeAfterOptionsWorker(const ir::AstNode *node)
234 {
235 InsertNodeOptions options;
236 switch (node->Type()) {
237 case ark::es2panda::ir::AstNodeType::CLASS_DECLARATION:
238 case ark::es2panda::ir::AstNodeType::STRUCT_DECLARATION:
239 case ark::es2panda::ir::AstNodeType::TS_MODULE_DECLARATION:
240 options.prefix = "\n";
241 options.suffix = "\n";
242 return options;
243 case ark::es2panda::ir::AstNodeType::VARIABLE_DECLARATION:
244 case ark::es2panda::ir::AstNodeType::STRING_LITERAL:
245 case ark::es2panda::ir::AstNodeType::IDENTIFIER:
246 options.prefix = ", ";
247 return options;
248 case ark::es2panda::ir::AstNodeType::PROPERTY:
249 options.suffix = "," + std::string("\n");
250 return options;
251 case ark::es2panda::ir::AstNodeType::EXPORT_SPECIFIER:
252 options.prefix = ", ";
253 return options;
254 case ark::es2panda::ir::AstNodeType::TS_TYPE_PARAMETER:
255 return options;
256 default:
257 // Else we haven't handled this kind of node yet -- add it
258 options.suffix = "\n";
259 return options;
260 }
261 }
262 struct StartandEndOfNode {
263 size_t start;
264 size_t end;
265 };
266
GetClassOrObjectBraceEnds(ir::AstNode * node)267 StartandEndOfNode GetClassOrObjectBraceEnds(ir::AstNode *node)
268 {
269 const auto open = node->Start().index;
270 const auto close = node->End().index;
271 return StartandEndOfNode {open, close};
272 }
273
FinishClassesWithNodesInsertedAtStart()274 void ChangeTracker::FinishClassesWithNodesInsertedAtStart()
275 {
276 for (const auto mapElem : classesWithNodesInsertedAtStart_) {
277 StartandEndOfNode braceEnds = GetClassOrObjectBraceEnds(mapElem.second.node);
278 const auto isEmpty = GetMembersOrProperties(mapElem.second.node).empty();
279 const auto isSingleLine = mapElem.second.node->Start().line == mapElem.second.node->End().line;
280 if (isEmpty && isSingleLine) {
281 // For `class C { }` remove the whitespace inside the braces.
282 DeleteRange(mapElem.second.sourceFile, CreateRange(braceEnds.start, braceEnds.end - 1));
283 }
284 if (isSingleLine) {
285 InsertText(mapElem.second.sourceFile, braceEnds.end - 1, "\n");
286 }
287 }
288 }
289
FinishDeleteDeclarations()290 void ChangeTracker::FinishDeleteDeclarations()
291 {
292 // its about delete declarations
293 // will develop next version
294 // its about delete declarations
295 }
296 /* createTextrangeFromSpan did not developed. it will develop next version.it should be gotten from utılıtıes
297 * createTextTangeFromSpan method. pls check it from ts side*/
PushRaw(const SourceFile * sourceFile,const FileTextChanges & change)298 void ChangeTracker::PushRaw(const SourceFile *sourceFile, const FileTextChanges &change)
299 {
300 for (const auto &c : change.textChanges) {
301 ChangeText changeText {
302 sourceFile, {c.span.start, c.span.start + c.newText.length()}, ChangeKind::TEXT, c.newText};
303 changes_.emplace_back(changeText);
304 }
305 }
306
DeleteRange(const SourceFile * sourceFile,TextRange range)307 void ChangeTracker::DeleteRange(const SourceFile *sourceFile, TextRange range)
308 {
309 RemoveNode removeNode = {
310 sourceFile,
311 range,
312 ChangeKind::REMOVE,
313 };
314 changes_.emplace_back(removeNode);
315 }
Delete(const SourceFile * sourceFile,std::variant<const ir::AstNode *,const std::vector<const ir::AstNode * >> & node)316 void ChangeTracker::Delete(const SourceFile *sourceFile,
317 std::variant<const ir::AstNode *, const std::vector<const ir::AstNode *>> &node)
318 {
319 if (std::holds_alternative<const std::vector<const ir::AstNode *>>(node)) {
320 std::vector<const ir::AstNode *> constNodes;
321 auto nodes = std::get<const std::vector<const ir::AstNode *>>(node);
322 constNodes.reserve(nodes.size());
323 for (auto n : nodes) {
324 constNodes.emplace_back(n);
325 }
326 deletedNodes_.push_back({sourceFile, constNodes});
327 } else {
328 deletedNodes_.push_back({sourceFile, node});
329 }
330 }
GetAdjustedRange(es2panda_Context * context,ir::AstNode * startNode,ir::AstNode * endNode)331 TextRange ChangeTracker::GetAdjustedRange(es2panda_Context *context, ir::AstNode *startNode, ir::AstNode *endNode)
332 {
333 auto *ctx = reinterpret_cast<ark::es2panda::public_lib::Context *>(context);
334
335 const auto startPosition = GetAdjustedLocation(startNode, false, ctx->allocator);
336 const auto endPosition = GetAdjustedLocation(endNode, false, ctx->allocator);
337 return {(*startPosition)->Start().index, (*endPosition)->End().index};
338 }
339
DeleteNode(es2panda_Context * context,const SourceFile * sourceFile,ir::AstNode * node)340 void ChangeTracker::DeleteNode(es2panda_Context *context, const SourceFile *sourceFile, ir::AstNode *node)
341 {
342 const auto adjustedRange = GetAdjustedRange(context, node, node);
343 DeleteRange(sourceFile, adjustedRange);
344 }
345
DeleteNodeRange(es2panda_Context * context,ir::AstNode * startNode,ir::AstNode * endNode)346 void ChangeTracker::DeleteNodeRange(es2panda_Context *context, ir::AstNode *startNode, ir::AstNode *endNode)
347 {
348 auto *ctx = reinterpret_cast<ark::es2panda::public_lib::Context *>(context);
349
350 const auto startPosition = GetAdjustedLocation(startNode, false, ctx->allocator);
351 const auto endPosition = GetAdjustedLocation(endNode, false, ctx->allocator);
352 const auto sourceFile = ctx->sourceFile;
353 DeleteRange(sourceFile, {(*startPosition)->Start().index, (*endPosition)->End().index});
354 }
355
DeleteModifier(es2panda_Context * context,ir::AstNode * modifier)356 void ChangeTracker::DeleteModifier(es2panda_Context *context, ir::AstNode *modifier)
357 {
358 auto astContext = reinterpret_cast<ark::es2panda::public_lib::Context *>(context);
359 const auto sourceFile = astContext->sourceFile;
360 DeleteRange(sourceFile, {modifier->Start().index, modifier->End().index}); // skipTrivia method will ask
361 }
362
DeleteNodeRangeExcludingEnd(es2panda_Context * context,ir::AstNode * startNode,ir::AstNode * afterEndNode)363 void ChangeTracker::DeleteNodeRangeExcludingEnd(es2panda_Context *context, ir::AstNode *startNode,
364 ir::AstNode *afterEndNode)
365 {
366 auto astContext = reinterpret_cast<ark::es2panda::public_lib::Context *>(context);
367 const auto sourceFile = astContext->sourceFile;
368
369 DeleteRange(sourceFile, GetAdjustedRange(context, startNode, afterEndNode));
370 }
371
ReplaceRange(es2panda_Context * context,TextRange range,const ir::AstNode * newNode,InsertNodeOptions & options)372 void ChangeTracker::ReplaceRange(es2panda_Context *context, TextRange range, const ir::AstNode *newNode,
373 InsertNodeOptions &options)
374 {
375 auto astContext = reinterpret_cast<ark::es2panda::public_lib::Context *>(context);
376 const auto sourceFile = astContext->sourceFile;
377 ReplaceWithSingleNode replaceNode = {sourceFile, range, ChangeKind::REPLACEWITHSINGLENODE, newNode, options};
378 changes_.emplace_back(replaceNode);
379 }
380
ReplaceNode(es2panda_Context * context,ir::AstNode * oldNode,ir::AstNode * newNode,ChangeNodeOptions options)381 void ChangeTracker::ReplaceNode(es2panda_Context *context, ir::AstNode *oldNode, ir::AstNode *newNode,
382 ChangeNodeOptions options)
383 {
384 const auto adjRange = GetAdjustedRange(context, oldNode, oldNode);
385 InsertNodeOptions insertOptions;
386 if (options.insertNodeOptions.has_value()) {
387 insertOptions = *options.insertNodeOptions;
388 }
389 ReplaceRange(context, adjRange, newNode, insertOptions);
390 }
391
ReplaceNodeRange(es2panda_Context * context,ir::AstNode * startNode,ir::AstNode * endNode,ir::AstNode * newNode)392 void ChangeTracker::ReplaceNodeRange(es2panda_Context *context, ir::AstNode *startNode, ir::AstNode *endNode,
393 ir::AstNode *newNode)
394 {
395 const auto adjRange = GetAdjustedRange(context, startNode, endNode);
396 InsertNodeOptions options;
397 ReplaceRange(context, adjRange, newNode, options);
398 }
399
ReplaceNodeWithNodes(es2panda_Context * context,ir::AstNode * oldNode,std::vector<ir::AstNode * > & newNodes)400 void ChangeTracker::ReplaceNodeWithNodes(es2panda_Context *context, ir::AstNode *oldNode,
401 std::vector<ir::AstNode *> &newNodes)
402 {
403 const auto adjRange = GetAdjustedRange(context, oldNode, oldNode);
404 ReplaceRangeWithNodes(context, adjRange, newNodes);
405 }
406
ReplaceNodeWithText(es2panda_Context * context,ir::AstNode * oldNode,const std::string & text)407 void ChangeTracker::ReplaceNodeWithText(es2panda_Context *context, ir::AstNode *oldNode, const std::string &text)
408 {
409 auto astContext = reinterpret_cast<ark::es2panda::public_lib::Context *>(context);
410 const auto sourceFile = astContext->sourceFile;
411 const auto adjRange = GetAdjustedRange(context, oldNode, oldNode);
412 ReplaceRangeWithText(sourceFile, adjRange, text);
413 }
414
ReplaceRangeWithText(const SourceFile * sourceFile,TextRange range,const std::string & text)415 void ChangeTracker::ReplaceRangeWithText(const SourceFile *sourceFile, TextRange range, const std::string &text)
416 {
417 ChangeText change = {sourceFile, range, ChangeKind::TEXT, text};
418 changes_.emplace_back(change);
419 }
420
ReplaceNodeRangeWithNodes(es2panda_Context * context,ir::AstNode * startNode,ir::AstNode * endNode,std::vector<ir::AstNode * > & newNodes)421 void ChangeTracker::ReplaceNodeRangeWithNodes(es2panda_Context *context, ir::AstNode *startNode, ir::AstNode *endNode,
422 std::vector<ir::AstNode *> &newNodes)
423 {
424 ReplaceRangeWithNodes(context, GetAdjustedRange(context, startNode, endNode), newNodes);
425 }
426
CreateRange(size_t pos,size_t end)427 TextRange ChangeTracker::CreateRange(size_t pos, size_t end)
428 {
429 if (end == 0) {
430 end = pos;
431 }
432 return {pos, end};
433 }
434
ReplacePropertyAssignment(es2panda_Context * context,ir::AstNode * oldNode,ir::AstNode * newNode)435 void ChangeTracker::ReplacePropertyAssignment(es2panda_Context *context, ir::AstNode *oldNode, ir::AstNode *newNode)
436 {
437 const auto suffix = NextCommaToken(context, oldNode) != nullptr ? "" : ("," + std::string("\n"));
438 InsertNodeOptions insertOptions;
439 insertOptions.suffix = suffix;
440 ChangeNodeOptions options = {g_useNonAdjustedPositions, insertOptions};
441 ReplaceNode(context, oldNode, newNode, options);
442 }
443
ReplaceConstructorBody(es2panda_Context * context,ir::AstNode * ctr,const std::vector<ir::Statement * > & statements)444 void ChangeTracker::ReplaceConstructorBody(es2panda_Context *context, ir::AstNode *ctr,
445 const std::vector<ir::Statement *> &statements)
446 {
447 if (statements.empty()) {
448 ChangeNodeOptions options = {};
449 ReplaceNode(context, ctr, nullptr,
450 options); // newNode should get from factory.createBlock(statements, / *multiLine* ̇/ true)
451 }
452 }
453
IsLineBreak(char ch)454 bool ChangeTracker::IsLineBreak(char ch)
455 {
456 const auto lineFeed = '\n';
457 const auto carriageReturn = '\r';
458 return ch == lineFeed || ch == carriageReturn;
459 }
InsertNodeAt(es2panda_Context * context,size_t pos,const ir::AstNode * newNode,InsertNodeOptions & options)460 void ChangeTracker::InsertNodeAt(es2panda_Context *context, size_t pos, const ir::AstNode *newNode,
461 InsertNodeOptions &options)
462 {
463 ReplaceRange(context, CreateRange(pos), newNode, options);
464 }
465
GetInsertionPositionAtSourceFileTop(ir::AstNode * sourceFileAst)466 size_t ChangeTracker::GetInsertionPositionAtSourceFileTop(ir::AstNode *sourceFileAst)
467 {
468 const auto topOfFile = sourceFileAst->FindChild([](ir::AstNode *child) { return child->IsClassDeclaration(); });
469 return topOfFile->Start().index;
470 }
471
InsertNodeAtTopOfFile(es2panda_Context * context,ir::AstNode * newNode,bool blankLineBetween)472 void ChangeTracker::InsertNodeAtTopOfFile(es2panda_Context *context, ir::AstNode *newNode, bool blankLineBetween)
473 {
474 InsertAtTopOfFile(context, newNode, blankLineBetween);
475 }
476
InsertNodeBefore(es2panda_Context * context,ir::AstNode * before,ir::AstNode * newNode,bool blankLineBetween)477 void ChangeTracker::InsertNodeBefore(es2panda_Context *context, ir::AstNode *before, ir::AstNode *newNode,
478 bool blankLineBetween)
479 {
480 InsertNodeOptions insertOptions = GetOptionsForInsertNodeBefore(before, newNode, blankLineBetween);
481 auto ctx = reinterpret_cast<public_lib::Context *>(context);
482 auto startpos = GetAdjustedLocation(before, false, ctx->allocator);
483 InsertNodeAt(context, (*startpos)->Start().index, newNode, insertOptions);
484 }
485
InsertModifierAt(es2panda_Context * context,const size_t pos,const ir::AstNode * modifier,InsertNodeOptions & options)486 void ChangeTracker::InsertModifierAt(es2panda_Context *context, const size_t pos, const ir::AstNode *modifier,
487 InsertNodeOptions &options)
488 {
489 (void)modifier;
490 InsertNodeAt(context, pos, nullptr, options); // newNode should get from factory.createToken(modifier)
491 }
492
InsertModifierBefore(es2panda_Context * context,const ir::AstNode * modifier,ir::AstNode * before)493 void ChangeTracker::InsertModifierBefore(es2panda_Context *context, const ir::AstNode *modifier, ir::AstNode *before)
494 {
495 InsertNodeOptions options;
496 options.suffix = " ";
497 return InsertModifierAt(context, before->Start().index, modifier, options);
498 }
499
InsertText(const SourceFile * sourceFile,size_t pos,const std::string & text)500 void ChangeTracker::InsertText(const SourceFile *sourceFile, size_t pos, const std::string &text)
501 {
502 ReplaceRangeWithText(sourceFile, CreateRange(pos), text);
503 }
504
505 /** Prefer this over replacing a node with another that has a type annotation,
506 * as it avoids reformatting the other parts of the node. */
TryInsertTypeAnnotation(es2panda_Context * context,ir::AstNode * node,ir::AstNode * type)507 bool ChangeTracker::TryInsertTypeAnnotation(es2panda_Context *context, ir::AstNode *node, ir::AstNode *type)
508 {
509 InsertNodeOptions options;
510 options.prefix = ": ";
511 InsertNodeAt(context, node->End().index, type, options);
512 return true;
513 }
514
TryInsertThisTypeAnnotation(es2panda_Context * context,ir::AstNode * node,ir::AstNode * type)515 void ChangeTracker::TryInsertThisTypeAnnotation(es2panda_Context *context, ir::AstNode *node, ir::AstNode *type)
516 {
517 InsertNodeOptions options;
518 options.prefix = "this: ";
519 if (node->IsFunctionExpression()) {
520 options.suffix = node->AsFunctionExpression()->Function()->Params().empty() ? ", " : "";
521 }
522
523 InsertNodeAt(context, node->Start().index, type, options);
524 }
525
InsertTypeParameters(es2panda_Context * context,const ir::AstNode * node,std::vector<ir::AstNode * > & typeParameters)526 void ChangeTracker::InsertTypeParameters(es2panda_Context *context, const ir::AstNode *node,
527 std::vector<ir::AstNode *> &typeParameters)
528 {
529 size_t start;
530 if (node->IsFunctionDeclaration()) {
531 start = node->AsFunctionExpression()->Function()->Params().at(0)->End().index;
532 } else {
533 start = node->End().index;
534 }
535 ReplaceWithMultipleNodesOptions options;
536 options.prefix = "<";
537 options.suffix = ">";
538 options.joiner = ", ";
539 InsertNodesAt(context, start, typeParameters, options);
540 }
541
InsertNodeAtConstructorStart(es2panda_Context * context,ir::AstNode * ctr,ir::Statement * newStatement)542 void ChangeTracker::InsertNodeAtConstructorStart(es2panda_Context *context, ir::AstNode *ctr,
543 ir::Statement *newStatement)
544 {
545 if (!ctr->IsConstructor()) {
546 return;
547 }
548 std::vector<ir::Statement *> statements;
549 ir::Statement *superStatement;
550
551 ctr->FindChild([&statements, &superStatement](ir::AstNode *n) {
552 if (n->IsSuperExpression()) {
553 superStatement = n->AsStatement();
554 return true;
555 }
556 if (n->IsStatement()) {
557 statements.push_back(n->AsStatement());
558 }
559
560 return false;
561 });
562 if (superStatement == nullptr && !statements.empty()) {
563 ReplaceConstructorBody(context, ctr, statements);
564 } else {
565 if (superStatement != nullptr) {
566 InsertNodeAfter(context, superStatement, newStatement->AsStatement());
567 } else {
568 InsertNodeBefore(context, ctr, newStatement->AsStatement());
569 }
570 }
571 }
572
InsertNodeAfter(es2panda_Context * context,ir::AstNode * after,ir::AstNode * newNode)573 void ChangeTracker::InsertNodeAfter(es2panda_Context *context, ir::AstNode *after, ir::AstNode *newNode)
574 {
575 const auto endPosition = InsertNodeAfterWorker(context, after, newNode);
576 InsertNodeOptions options = GetInsertNodeAfterOptions(after);
577 InsertNodeAt(context, endPosition, newNode, options);
578 }
579
InsertNodeAtConstructorEnd(es2panda_Context * context,ir::AstNode * ctr,ir::Statement * newStatement)580 void ChangeTracker::InsertNodeAtConstructorEnd(es2panda_Context *context, ir::AstNode *ctr, ir::Statement *newStatement)
581 {
582 if (!ctr->IsConstructor()) {
583 return;
584 }
585 std::vector<ir::Statement *> statements;
586 ctr->FindChild([&statements](ir::AstNode *n) {
587 if (n->IsStatement()) {
588 statements.push_back(n->AsStatement());
589 }
590 return false;
591 });
592
593 if (statements.empty()) {
594 ReplaceConstructorBody(context, ctr, statements);
595 } else {
596 InsertNodeAfter(context, statements[statements.size() - 1], newStatement);
597 }
598 }
599
InsertNodeAtEndOfScope(es2panda_Context * context,ir::AstNode * scope,ir::AstNode * newNode)600 void ChangeTracker::InsertNodeAtEndOfScope(es2panda_Context *context, ir::AstNode *scope, ir::AstNode *newNode)
601 {
602 InsertNodeOptions options;
603 options.prefix = "\n";
604 options.suffix = "\n";
605 InsertNodeAt(context, scope->End().index, newNode, options);
606 }
607
InsertMemberAtStart(es2panda_Context * context,ir::AstNode * node,ir::AstNode * newElement)608 void ChangeTracker::InsertMemberAtStart(es2panda_Context *context, ir::AstNode *node, ir::AstNode *newElement)
609 {
610 if (node == nullptr || newElement == nullptr) {
611 return;
612 }
613 if (node->IsClassDeclaration() || node->IsTSInterfaceDeclaration() || node->IsTSTypeLiteral() ||
614 node->IsObjectExpression()) {
615 if (newElement->IsClassProperty() || newElement->IsTSPropertySignature() || newElement->IsTSMethodSignature()) {
616 InsertNodeAtStartWorker(context, node, newElement);
617 }
618 }
619 }
620
InsertNodeAtObjectStart(es2panda_Context * context,ir::ObjectExpression * obj,ir::AstNode * newElement)621 void ChangeTracker::InsertNodeAtObjectStart(es2panda_Context *context, ir::ObjectExpression *obj,
622 ir::AstNode *newElement)
623 {
624 InsertNodeAtStartWorker(context, obj, newElement);
625 }
626
InsertNodeAfterComma(es2panda_Context * context,ir::AstNode * after,ir::AstNode * newNode)627 void ChangeTracker::InsertNodeAfterComma(es2panda_Context *context, ir::AstNode *after, ir::AstNode *newNode)
628 {
629 const auto endPosition = InsertNodeAfterWorker(context, NextCommaToken(context, after), newNode);
630 InsertNodeOptions options = GetInsertNodeAfterOptions(after);
631 InsertNodeAt(context, endPosition, newNode, options);
632 }
633
InsertNodeAtEndOfList(es2panda_Context * context,std::vector<const ir::AstNode * > & list,ir::AstNode * newNode)634 void ChangeTracker::InsertNodeAtEndOfList(es2panda_Context *context, std::vector<const ir::AstNode *> &list,
635 ir::AstNode *newNode)
636 {
637 InsertNodeOptions options;
638 options.prefix = ", ";
639 const auto size = list.size();
640 InsertNodeAt(context, size - 1, newNode, options);
641 }
GetInsertNodeAfterOptions(const ir::AstNode * after)642 InsertNodeOptions ChangeTracker::GetInsertNodeAfterOptions(const ir::AstNode *after)
643 {
644 return GetInsertNodeAfterOptionsWorker(after);
645 }
646
InsertNodesAfter(es2panda_Context * context,ir::AstNode * after,std::vector<ir::AstNode * > newNodes)647 void ChangeTracker::InsertNodesAfter(es2panda_Context *context, ir::AstNode *after, std::vector<ir::AstNode *> newNodes)
648 {
649 const auto endPosition = InsertNodeAfterWorker(context, after, newNodes.at(0));
650 InsertNodeOptions insertOptions = GetInsertNodeAfterOptions(after);
651 ReplaceWithMultipleNodesOptions afterOptions;
652 afterOptions.prefix = insertOptions.prefix;
653 afterOptions.suffix = insertOptions.suffix;
654 InsertNodesAt(context, endPosition, newNodes, afterOptions);
655 }
656
InsertFirstParameter(es2panda_Context * context,std::vector<ir::TSTypeParameterDeclaration * > parameters,ir::TSTypeParameterDeclaration newParam)657 void ChangeTracker::InsertFirstParameter(es2panda_Context *context,
658 std::vector<ir::TSTypeParameterDeclaration *> parameters,
659 ir::TSTypeParameterDeclaration newParam)
660 {
661 if (parameters.empty()) {
662 InsertNodeBefore(context, parameters[0], newParam.AsTSTypeParameterDeclaration());
663 } else {
664 InsertNodeOptions insertOptions;
665 InsertNodeAt(context, parameters.size(), newParam.AsTSTypeParameterDeclaration(), insertOptions);
666 }
667 }
668
InsertExportModifier(const SourceFile * sourceFile,ir::Statement * node)669 void ChangeTracker::InsertExportModifier(const SourceFile *sourceFile, ir::Statement *node)
670 {
671 const std::basic_string exportModifier = "export ";
672 InsertText(sourceFile, node->Start().index, exportModifier);
673 }
674
GetContainingList(ir::AstNode * node)675 std::vector<ir::AstNode *> ChangeTracker::GetContainingList(ir::AstNode *node)
676 {
677 std::vector<ir::AstNode *> containingList;
678 node->Parent()->FindChild([&containingList](ir::AstNode *child) {
679 if (child->IsObjectExpression() || child->IsObjectExpression()) {
680 for (auto *property : child->AsObjectExpression()->Properties()) {
681 containingList.push_back(property);
682 }
683 return true;
684 }
685 return false;
686 });
687 return containingList;
688 }
689
690 /**
691 * This function should be used to insert nodes in lists when nodes don't carry
692 * separators as the part of the node range, i.e. arguments in arguments lists,
693 * parameters in parameter lists etc. Note that separators are part of the node
694 * in statements and class elements.
695 */
696
InsertNodeInListAfterMultiLine(bool multilineList,es2panda_Context * context,const SourceFile * sourceFile,size_t end,const ir::AstNode * newNode)697 void ChangeTracker::InsertNodeInListAfterMultiLine(bool multilineList, es2panda_Context *context,
698 const SourceFile *sourceFile, size_t end, const ir::AstNode *newNode)
699 {
700 if (multilineList) {
701 InsertNodeOptions insertOptions;
702 ReplaceRange(context, CreateRange(end), nullptr,
703 insertOptions); // newNode should get from factory.createToken(separator)
704 const int indentation = 4;
705 size_t insertPos = 4;
706 while (insertPos != end && IsLineBreak(sourceFile->source.at(insertPos - 1))) {
707 insertPos--;
708 }
709 insertOptions.indentation = indentation;
710 insertOptions.prefix = "\n";
711 ReplaceRange(context, CreateRange(insertPos), newNode, insertOptions);
712 } else {
713 InsertNodeOptions insertOptions;
714 insertOptions.prefix = " ";
715 ReplaceRange(context, CreateRange(end), newNode, insertOptions);
716 }
717 }
718
InsertNodeInListAfter(es2panda_Context * context,ir::AstNode * after,ir::AstNode * newNode,std::vector<ir::AstNode * > & containingList)719 void ChangeTracker::InsertNodeInListAfter(es2panda_Context *context, ir::AstNode *after, ir::AstNode *newNode,
720 std::vector<ir::AstNode *> &containingList)
721 {
722 std::vector<ir::AstNode *> containingListResult = GetContainingList(after);
723 containingList = std::vector<ir::AstNode *>(containingListResult.begin(), containingListResult.end());
724 if (containingList.empty()) {
725 return;
726 }
727 size_t index = 0;
728 for (size_t i = 0; i < containingList.size(); i++) {
729 if (containingList[i] == after) {
730 index = i;
731 break;
732 }
733 }
734 if (index == 0) {
735 return;
736 }
737 const auto end = after->End().index;
738 Initializer initializer = Initializer();
739 auto astContext = reinterpret_cast<ark::es2panda::public_lib::Context *>(context);
740 auto sourceFile = astContext->sourceFile;
741 if (index != containingList.size() - 1) {
742 const auto nextToken = sourceFile->source.at(after->End().index);
743 if (nextToken != 0 && (nextToken == ',' || nextToken == ';' || nextToken == ':' || nextToken == '.')) {
744 const auto nextNode = containingList[index + 1];
745 const auto startPos = nextNode->Start().index;
746 ReplaceWithMultipleNodesOptions options;
747 options.suffix = std::string(1, nextToken);
748 InsertNodesAt(context, startPos, containingList, options);
749 } else {
750 bool multilineList = false;
751 char separator;
752 if (containingList.size() == 1) {
753 separator = ',';
754 } else {
755 const auto tokenBeforeInsertPosition =
756 FindPrecedingToken(after->Start().index, after, initializer.Allocator());
757 separator = ',';
758 const auto afterMinusOneStartLinePosition =
759 GetStartPositionOfLine(containingList.at(index - 1)->Start().line, context);
760 multilineList = containingList[index - 1]->Start().line != containingList[index]->Start().line;
761 (void)tokenBeforeInsertPosition; // Use tokenBeforeInsertPosition if needed
762 (void)afterMinusOneStartLinePosition; // Use afterMinusOneStartLinePosition if needed
763 }
764 (void)separator; // Use separator if needed
765 InsertNodeInListAfterMultiLine(multilineList, context, sourceFile, end, newNode);
766 }
767 }
768 }
769
InsertImportSpecifierAtIndex(es2panda_Context * context,ir::AstNode * importSpecifier,std::vector<ir::AstNode * > & namedImports,size_t index)770 void ChangeTracker::InsertImportSpecifierAtIndex(es2panda_Context *context, ir::AstNode *importSpecifier,
771 std::vector<ir::AstNode *> &namedImports, size_t index)
772 {
773 const auto prevSpecifier = namedImports.at(index - 1);
774 if (prevSpecifier != nullptr) {
775 InsertNodeInListAfter(context, prevSpecifier, nullptr, namedImports);
776 } else {
777 InsertNodeBefore(context, namedImports[0], importSpecifier,
778 namedImports[0]->Parent()->Start().index == namedImports[0]->Parent()->End().index);
779 }
780 }
781
GetTextChangesFromChanges(Change & changes,std::string & newLineCharacter,FormattingContext & formattingContext)782 std::vector<FileTextChanges> GetTextChangesFromChanges(
783 Change &changes, std::string &newLineCharacter,
784 FormattingContext &formattingContext) // ValidateNonFormattedText should add
785 {
786 // will develop with changestoText
787 // Its about finishing changes processes
788 (void)changes;
789 (void)newLineCharacter;
790 (void)formattingContext;
791
792 return {};
793 }
GetTextChangesFromChanges(std::vector<Change> & changes,std::string & newLineCharacter,const FormatCodeSettings & formatCodeSettings)794 std::vector<FileTextChanges> ChangeTracker::GetTextChangesFromChanges(std::vector<Change> &changes,
795 std::string &newLineCharacter,
796 const FormatCodeSettings &formatCodeSettings)
797 {
798 (void)newLineCharacter;
799 (void)formatCodeSettings;
800
801 std::unordered_map<std::string, FileTextChanges> fileChangesMap;
802 for (const auto &change : changes) {
803 if (const auto *textChange = std::get_if<ark::es2panda::lsp::ChangeText>(&change)) {
804 TextChange c = {{textChange->range.pos, textChange->range.end - textChange->range.pos}, textChange->text};
805 const std::string &filePath = std::string(textChange->sourceFile->filePath);
806 if (fileChangesMap.find(filePath) == fileChangesMap.end()) {
807 fileChangesMap[filePath].fileName = filePath;
808 }
809 fileChangesMap[filePath].textChanges.push_back(c);
810 }
811 }
812
813 std::vector<FileTextChanges> fileTextChanges;
814 fileTextChanges.reserve(fileChangesMap.size());
815 for (auto &pair : fileChangesMap) {
816 fileTextChanges.push_back(std::move(pair.second));
817 }
818
819 return fileTextChanges;
820 }
821
822 /**
823 * Note: after calling this, the TextChanges object must be discarded!
824 * @param validate only for tests
825 * The reason we must validate as part of this method is that
826 * `getNonFormattedText` changes the node's positions, so we can only call this
827 * once and can't get the non-formatted text separately.
828 */
GetChanges()829 std::vector<FileTextChanges> ChangeTracker::GetChanges() // should add ValidateNonFormattedText
830 {
831 FinishDeleteDeclarations();
832 FinishClassesWithNodesInsertedAtStart();
833 auto textChangesList =
834 GetTextChangesFromChanges(changes_, newLineCharacter_, formatContext_.GetFormatCodeSettings());
835
836 return textChangesList;
837 }
838
CreateNewFile(SourceFile * oldFile,const std::string & fileName,std::vector<const ir::Statement * > & statements)839 void ChangeTracker::CreateNewFile(SourceFile *oldFile, const std::string &fileName,
840 std::vector<const ir::Statement *> &statements)
841 {
842 NewFile newFile;
843 newFile.oldFile = oldFile;
844 newFile.fileName = fileName;
845 newFile.statements = statements;
846 newFiles_.push_back(newFile);
847 }
848 } // namespace ark::es2panda::lsp