1 /****************************************************************************
2 **
3 ** Copyright (C) 2016 The Qt Company Ltd.
4 ** Contact: https://www.qt.io/licensing/
5 **
6 ** This file is part of Qbs.
7 **
8 ** $QT_BEGIN_LICENSE:LGPL$
9 ** Commercial License Usage
10 ** Licensees holding valid commercial Qt licenses may use this file in
11 ** accordance with the commercial license agreement provided with the
12 ** Software or, alternatively, in accordance with the terms contained in
13 ** a written agreement between you and The Qt Company. For licensing terms
14 ** and conditions see https://www.qt.io/terms-conditions. For further
15 ** information use the contact form at https://www.qt.io/contact-us.
16 **
17 ** GNU Lesser General Public License Usage
18 ** Alternatively, this file may be used under the terms of the GNU Lesser
19 ** General Public License version 3 as published by the Free Software
20 ** Foundation and appearing in the file LICENSE.LGPL3 included in the
21 ** packaging of this file. Please review the following information to
22 ** ensure the GNU Lesser General Public License version 3 requirements
23 ** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
24 **
25 ** GNU General Public License Usage
26 ** Alternatively, this file may be used under the terms of the GNU
27 ** General Public License version 2.0 or (at your option) the GNU General
28 ** Public license version 3 or any later version approved by the KDE Free
29 ** Qt Foundation. The licenses are as published by the Free Software
30 ** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
31 ** included in the packaging of this file. Please review the following
32 ** information to ensure the GNU General Public License requirements will
33 ** be met: https://www.gnu.org/licenses/gpl-2.0.html and
34 ** https://www.gnu.org/licenses/gpl-3.0.html.
35 **
36 ** $QT_END_LICENSE$
37 **
38 ****************************************************************************/
39
40 #include "qmljsrewriter.h"
41
42 #include <parser/qmljsast_p.h>
43
44 namespace QbsQmlJS {
45 using namespace AST;
46
toString(UiQualifiedId * qualifiedId,QChar delimiter=QLatin1Char ('.'))47 static QString toString(UiQualifiedId *qualifiedId, QChar delimiter = QLatin1Char('.'))
48 {
49 QString result;
50
51 for (UiQualifiedId *iter = qualifiedId; iter; iter = iter->next) {
52 if (iter != qualifiedId)
53 result += delimiter;
54
55 result += iter->name;
56 }
57
58 return result;
59 }
60
61
Rewriter(QString originalText,ChangeSet * changeSet,QStringList propertyOrder)62 Rewriter::Rewriter(QString originalText,
63 ChangeSet *changeSet,
64 QStringList propertyOrder)
65 : m_originalText(std::move(originalText))
66 , m_changeSet(changeSet)
67 , m_propertyOrder(std::move(propertyOrder))
68 {
69 Q_ASSERT(changeSet);
70 }
71
addBinding(AST::UiObjectInitializer * ast,const QString & propertyName,const QString & propertyValue,BindingType bindingType)72 Rewriter::Range Rewriter::addBinding(AST::UiObjectInitializer *ast,
73 const QString &propertyName,
74 const QString &propertyValue,
75 BindingType bindingType)
76 {
77 UiObjectMemberList *insertAfter = searchMemberToInsertAfter(ast->members,
78 propertyName,
79 m_propertyOrder);
80 return addBinding(ast, propertyName, propertyValue, bindingType, insertAfter);
81 }
82
addBinding(AST::UiObjectInitializer * ast,const QString & propertyName,const QString & propertyValue,BindingType bindingType,UiObjectMemberList * insertAfter)83 Rewriter::Range Rewriter::addBinding(AST::UiObjectInitializer *ast,
84 const QString &propertyName,
85 const QString &propertyValue,
86 BindingType bindingType,
87 UiObjectMemberList *insertAfter)
88 {
89 SourceLocation endOfPreviousMember;
90 SourceLocation startOfNextMember;
91
92 if (insertAfter == nullptr || insertAfter->member == nullptr) {
93 // insert as first member
94 endOfPreviousMember = ast->lbraceToken;
95
96 if (ast->members && ast->members->member)
97 startOfNextMember = ast->members->member->firstSourceLocation();
98 else
99 startOfNextMember = ast->rbraceToken;
100 } else {
101 endOfPreviousMember = insertAfter->member->lastSourceLocation();
102
103 if (insertAfter->next && insertAfter->next->member)
104 startOfNextMember = insertAfter->next->member->firstSourceLocation();
105 else
106 startOfNextMember = ast->rbraceToken;
107 }
108 const bool isOneLiner = endOfPreviousMember.startLine == startOfNextMember.startLine;
109 bool needsPreceedingSemicolon = false;
110 bool needsTrailingSemicolon = false;
111
112 if (isOneLiner) {
113 if (insertAfter == nullptr) { // we're inserting after an lbrace
114 if (ast->members) { // we're inserting before a member (and not the rbrace)
115 needsTrailingSemicolon = bindingType == ScriptBinding;
116 }
117 } else { // we're inserting after a member, not after the lbrace
118 if (endOfPreviousMember.isValid()) { // there already is a semicolon after the previous member
119 if (insertAfter->next && insertAfter->next->member) { // and the after us there is a member, not an rbrace, so:
120 needsTrailingSemicolon = bindingType == ScriptBinding;
121 }
122 } else { // there is no semicolon after the previous member (probably because there is an rbrace after us/it, so:
123 needsPreceedingSemicolon = true;
124 }
125 }
126 }
127
128 QString newPropertyTemplate;
129 switch (bindingType) {
130 case ArrayBinding:
131 newPropertyTemplate = QStringLiteral("%1: [\n%2\n]");
132 break;
133
134 case ObjectBinding:
135 newPropertyTemplate = QStringLiteral("%1: %2");
136 break;
137
138 case ScriptBinding:
139 newPropertyTemplate = QStringLiteral("%1: %2");
140 break;
141
142 default:
143 Q_ASSERT(!"unknown property type");
144 }
145
146 if (isOneLiner) {
147 if (needsPreceedingSemicolon)
148 newPropertyTemplate.prepend(QLatin1Char(';'));
149 newPropertyTemplate.prepend(QLatin1Char(' '));
150 if (needsTrailingSemicolon)
151 newPropertyTemplate.append(QLatin1Char(';'));
152 } else {
153 newPropertyTemplate.prepend(QLatin1Char('\n'));
154 }
155
156 m_changeSet->insert(endOfPreviousMember.end(),
157 newPropertyTemplate.arg(propertyName, propertyValue));
158
159 return {int(endOfPreviousMember.end()), int(endOfPreviousMember.end())};
160 }
161
searchMemberToInsertAfter(UiObjectMemberList * members,const QStringList & propertyOrder)162 UiObjectMemberList *Rewriter::searchMemberToInsertAfter(UiObjectMemberList *members,
163 const QStringList &propertyOrder)
164 {
165 const int objectDefinitionInsertionPoint = propertyOrder.indexOf(QString());
166
167 UiObjectMemberList *lastObjectDef = nullptr;
168 UiObjectMemberList *lastNonObjectDef = nullptr;
169
170 for (UiObjectMemberList *iter = members; iter; iter = iter->next) {
171 UiObjectMember *member = iter->member;
172 int idx = -1;
173
174 if (cast<UiObjectDefinition*>(member))
175 lastObjectDef = iter;
176 else if (auto arrayBinding = cast<UiArrayBinding*>(member))
177 idx = propertyOrder.indexOf(toString(arrayBinding->qualifiedId));
178 else if (auto objectBinding = cast<UiObjectBinding*>(member))
179 idx = propertyOrder.indexOf(toString(objectBinding->qualifiedId));
180 else if (auto scriptBinding = cast<UiScriptBinding*>(member))
181 idx = propertyOrder.indexOf(toString(scriptBinding->qualifiedId));
182 else if (cast<UiPublicMember*>(member))
183 idx = propertyOrder.indexOf(QLatin1String("property"));
184
185 if (idx < objectDefinitionInsertionPoint)
186 lastNonObjectDef = iter;
187 }
188
189 if (lastObjectDef)
190 return lastObjectDef;
191 else
192 return lastNonObjectDef;
193 }
194
searchMemberToInsertAfter(UiArrayMemberList * members,const QStringList & propertyOrder)195 UiArrayMemberList *Rewriter::searchMemberToInsertAfter(UiArrayMemberList *members,
196 const QStringList &propertyOrder)
197 {
198 const int objectDefinitionInsertionPoint = propertyOrder.indexOf(QString());
199
200 UiArrayMemberList *lastObjectDef = nullptr;
201 UiArrayMemberList *lastNonObjectDef = nullptr;
202
203 for (UiArrayMemberList *iter = members; iter; iter = iter->next) {
204 UiObjectMember *member = iter->member;
205 int idx = -1;
206
207 if (cast<UiObjectDefinition*>(member))
208 lastObjectDef = iter;
209 else if (auto arrayBinding = cast<UiArrayBinding*>(member))
210 idx = propertyOrder.indexOf(toString(arrayBinding->qualifiedId));
211 else if (auto objectBinding = cast<UiObjectBinding*>(member))
212 idx = propertyOrder.indexOf(toString(objectBinding->qualifiedId));
213 else if (auto scriptBinding = cast<UiScriptBinding*>(member))
214 idx = propertyOrder.indexOf(toString(scriptBinding->qualifiedId));
215 else if (cast<UiPublicMember*>(member))
216 idx = propertyOrder.indexOf(QLatin1String("property"));
217
218 if (idx < objectDefinitionInsertionPoint)
219 lastNonObjectDef = iter;
220 }
221
222 if (lastObjectDef)
223 return lastObjectDef;
224 else
225 return lastNonObjectDef;
226 }
227
searchMemberToInsertAfter(UiObjectMemberList * members,const QString & propertyName,const QStringList & propertyOrder)228 UiObjectMemberList *Rewriter::searchMemberToInsertAfter(UiObjectMemberList *members,
229 const QString &propertyName,
230 const QStringList &propertyOrder)
231 {
232 if (!members)
233 return nullptr; // empty members
234
235 QHash<QString, UiObjectMemberList *> orderedMembers;
236
237 for (UiObjectMemberList *iter = members; iter; iter = iter->next) {
238 UiObjectMember *member = iter->member;
239
240 if (auto arrayBinding = cast<UiArrayBinding*>(member))
241 orderedMembers[toString(arrayBinding->qualifiedId)] = iter;
242 else if (auto objectBinding = cast<UiObjectBinding*>(member))
243 orderedMembers[toString(objectBinding->qualifiedId)] = iter;
244 else if (cast<UiObjectDefinition*>(member))
245 orderedMembers[QString()] = iter;
246 else if (auto scriptBinding = cast<UiScriptBinding*>(member))
247 orderedMembers[toString(scriptBinding->qualifiedId)] = iter;
248 else if (cast<UiPublicMember*>(member))
249 orderedMembers[QStringLiteral("property")] = iter;
250 }
251
252 int idx = propertyOrder.indexOf(propertyName);
253 if (idx == -1)
254 idx = propertyOrder.indexOf(QString());
255 if (idx == -1)
256 idx = propertyOrder.size() - 1;
257
258 for (; idx > 0; --idx) {
259 const QString &prop = propertyOrder.at(idx - 1);
260 UiObjectMemberList *candidate = orderedMembers.value(prop, 0);
261 if (candidate != nullptr)
262 return candidate;
263 }
264
265 return nullptr;
266 }
267
changeBinding(UiObjectInitializer * ast,const QString & propertyName,const QString & newValue,BindingType binding)268 void Rewriter::changeBinding(UiObjectInitializer *ast,
269 const QString &propertyName,
270 const QString &newValue,
271 BindingType binding)
272 {
273 QString prefix, suffix;
274 int dotIdx = propertyName.indexOf(QLatin1Char('.'));
275 if (dotIdx != -1) {
276 prefix = propertyName.left(dotIdx);
277 suffix = propertyName.mid(dotIdx + 1);
278 }
279
280 for (UiObjectMemberList *members = ast->members; members; members = members->next) {
281 UiObjectMember *member = members->member;
282
283 // for non-grouped properties:
284 if (isMatchingPropertyMember(propertyName, member)) {
285 switch (binding) {
286 case ArrayBinding:
287 insertIntoArray(cast<UiArrayBinding*>(member), newValue);
288 break;
289
290 case ObjectBinding:
291 replaceMemberValue(member, newValue, false);
292 break;
293
294 case ScriptBinding:
295 replaceMemberValue(member, newValue, nextMemberOnSameLine(members));
296 break;
297
298 default:
299 Q_ASSERT(!"Unhandled QmlRefactoring::PropertyType");
300 }
301
302 break;
303 // for grouped properties:
304 } else if (!prefix.isEmpty()) {
305 if (auto def = cast<UiObjectDefinition *>(member)) {
306 if (toString(def->qualifiedTypeNameId) == prefix)
307 changeBinding(def->initializer, suffix, newValue, binding);
308 }
309 }
310 }
311 }
312
replaceMemberValue(UiObjectMember * propertyMember,const QString & newValue,bool needsSemicolon)313 void Rewriter::replaceMemberValue(UiObjectMember *propertyMember,
314 const QString &newValue,
315 bool needsSemicolon)
316 {
317 QString replacement = newValue;
318 int startOffset = -1;
319 int endOffset = -1;
320 if (auto objectBinding = AST::cast<UiObjectBinding *>(propertyMember)) {
321 startOffset = objectBinding->qualifiedTypeNameId->identifierToken.offset;
322 endOffset = objectBinding->initializer->rbraceToken.end();
323 } else if (auto scriptBinding = AST::cast<UiScriptBinding *>(propertyMember)) {
324 startOffset = scriptBinding->statement->firstSourceLocation().offset;
325 endOffset = scriptBinding->statement->lastSourceLocation().end();
326 } else if (auto arrayBinding = AST::cast<UiArrayBinding *>(propertyMember)) {
327 startOffset = arrayBinding->lbracketToken.offset;
328 endOffset = arrayBinding->rbracketToken.end();
329 } else if (auto publicMember = AST::cast<UiPublicMember*>(propertyMember)) {
330 if (publicMember->statement) {
331 startOffset = publicMember->statement->firstSourceLocation().offset;
332 if (publicMember->semicolonToken.isValid())
333 endOffset = publicMember->semicolonToken.end();
334 else
335 endOffset = publicMember->statement->lastSourceLocation().offset;
336 } else {
337 startOffset = publicMember->lastSourceLocation().end();
338 endOffset = startOffset;
339 if (publicMember->semicolonToken.isValid())
340 startOffset = publicMember->semicolonToken.offset;
341 replacement.prepend(QStringLiteral(": "));
342 }
343 } else {
344 return;
345 }
346
347 if (needsSemicolon)
348 replacement += QLatin1Char(';');
349
350 m_changeSet->replace(startOffset, endOffset, replacement);
351 }
352
isMatchingPropertyMember(const QString & propertyName,UiObjectMember * member)353 bool Rewriter::isMatchingPropertyMember(const QString &propertyName,
354 UiObjectMember *member)
355 {
356 if (auto publicMember = cast<UiPublicMember*>(member))
357 return publicMember->name == propertyName;
358 else if (auto objectBinding = cast<UiObjectBinding*>(member))
359 return toString(objectBinding->qualifiedId) == propertyName;
360 else if (auto scriptBinding = cast<UiScriptBinding*>(member))
361 return toString(scriptBinding->qualifiedId) == propertyName;
362 else if (auto arrayBinding = cast<UiArrayBinding*>(member))
363 return toString(arrayBinding->qualifiedId) == propertyName;
364 else
365 return false;
366 }
367
nextMemberOnSameLine(UiObjectMemberList * members)368 bool Rewriter::nextMemberOnSameLine(UiObjectMemberList *members)
369 {
370 if (members && members->next && members->next->member)
371 return members->next->member->firstSourceLocation().startLine == members->member->lastSourceLocation().startLine;
372 else
373 return false;
374 }
375
insertIntoArray(UiArrayBinding * ast,const QString & newValue)376 void Rewriter::insertIntoArray(UiArrayBinding *ast, const QString &newValue)
377 {
378 if (!ast)
379 return;
380
381 UiObjectMember *lastMember = nullptr;
382 for (UiArrayMemberList *iter = ast->members; iter; iter = iter->next) {
383 lastMember = iter->member;
384 }
385
386 if (!lastMember)
387 return;
388
389 const int insertionPoint = lastMember->lastSourceLocation().end();
390 m_changeSet->insert(insertionPoint, QLatin1String(",\n") + newValue);
391 }
392
removeBindingByName(UiObjectInitializer * ast,const QString & propertyName)393 void Rewriter::removeBindingByName(UiObjectInitializer *ast, const QString &propertyName)
394 {
395 QString prefix;
396 int dotIdx = propertyName.indexOf(QLatin1Char('.'));
397 if (dotIdx != -1)
398 prefix = propertyName.left(dotIdx);
399
400 for (UiObjectMemberList *it = ast->members; it; it = it->next) {
401 UiObjectMember *member = it->member;
402
403 // run full name match (for ungrouped properties):
404 if (isMatchingPropertyMember(propertyName, member)) {
405 removeMember(member);
406 // check for grouped properties:
407 } else if (!prefix.isEmpty()) {
408 if (auto def = cast<UiObjectDefinition *>(member)) {
409 if (toString(def->qualifiedTypeNameId) == prefix)
410 removeGroupedProperty(def, propertyName);
411 }
412 }
413 }
414 }
415
removeGroupedProperty(UiObjectDefinition * ast,const QString & propertyName)416 void Rewriter::removeGroupedProperty(UiObjectDefinition *ast,
417 const QString &propertyName)
418 {
419 int dotIdx = propertyName.indexOf(QLatin1Char('.'));
420 if (dotIdx == -1)
421 return;
422
423 const QString propName = propertyName.mid(dotIdx + 1);
424
425 UiObjectMember *wanted = nullptr;
426 unsigned memberCount = 0;
427 for (UiObjectMemberList *it = ast->initializer->members; it; it = it->next) {
428 ++memberCount;
429 UiObjectMember *member = it->member;
430
431 if (!wanted && isMatchingPropertyMember(propName, member))
432 wanted = member;
433 }
434
435 if (!wanted)
436 return;
437 if (memberCount == 1)
438 removeMember(ast);
439 else
440 removeMember(wanted);
441 }
442
removeMember(UiObjectMember * member)443 void Rewriter::removeMember(UiObjectMember *member)
444 {
445 int start = member->firstSourceLocation().offset;
446 int end = member->lastSourceLocation().end();
447
448 includeSurroundingWhitespace(m_originalText, start, end);
449
450 m_changeSet->remove(start, end);
451 }
452
includeSurroundingWhitespace(const QString & source,int & start,int & end)453 bool Rewriter::includeSurroundingWhitespace(const QString &source, int &start, int &end)
454 {
455 bool includeStartingWhitespace = true;
456 bool paragraphFound = false;
457 bool paragraphSkipped = false;
458
459 if (end >= 0) {
460 QChar c = source.at(end);
461
462 while (c.isSpace()) {
463 ++end;
464 if (c.unicode() == 10) {
465 paragraphFound = true;
466 paragraphSkipped = true;
467 break;
468 } else if (end == source.length()) {
469 break;
470 }
471
472 c = source.at(end);
473 }
474
475 includeStartingWhitespace = paragraphFound;
476 }
477
478 paragraphFound = false;
479 if (includeStartingWhitespace) {
480 while (start > 0) {
481 const QChar c = source.at(start - 1);
482
483 if (c.unicode() == 10) {
484 paragraphFound = true;
485 break;
486 }
487 if (!c.isSpace())
488 break;
489
490 --start;
491 }
492 }
493 if (!paragraphFound && paragraphSkipped) //keep the line ending
494 --end;
495
496 return paragraphFound;
497 }
498
includeLeadingEmptyLine(QStringView source,int & start)499 void Rewriter::includeLeadingEmptyLine(QStringView source, int &start)
500 {
501 if (start == 0)
502 return;
503
504 const qsizetype lineEnd = source.lastIndexOf(QChar::LineFeed, start);
505 if (lineEnd <= 0)
506 return;
507 const qsizetype lineStart = source.lastIndexOf(QChar::LineFeed, lineEnd - 1) + 1;
508 const auto line = source.mid(lineStart, lineEnd - lineStart);
509 if (!line.trimmed().isEmpty())
510 return;
511 start = lineStart;
512 }
513
includeEmptyGroupedProperty(UiObjectDefinition * groupedProperty,UiObjectMember * memberToBeRemoved,int & start,int & end)514 void Rewriter::includeEmptyGroupedProperty(UiObjectDefinition *groupedProperty, UiObjectMember *memberToBeRemoved, int &start, int &end)
515 {
516 if (groupedProperty->qualifiedTypeNameId && !groupedProperty->qualifiedTypeNameId->name.isEmpty()
517 && groupedProperty->qualifiedTypeNameId->name.at(0).isLower()) {
518 // grouped property
519 UiObjectMemberList *memberIter = groupedProperty->initializer->members;
520 while (memberIter) {
521 if (memberIter->member != memberToBeRemoved)
522 return;
523 memberIter = memberIter->next;
524 }
525 start = groupedProperty->firstSourceLocation().begin();
526 end = groupedProperty->lastSourceLocation().end();
527 }
528 }
529
530 #if 0
531 UiObjectMemberList *QMLRewriter::searchMemberToInsertAfter(UiObjectMemberList *members, const QStringList &propertyOrder)
532 {
533 const int objectDefinitionInsertionPoint = propertyOrder.indexOf(QString());
534
535 UiObjectMemberList *lastObjectDef = nullptr;
536 UiObjectMemberList *lastNonObjectDef = nullptr;
537
538 for (UiObjectMemberList *iter = members; iter; iter = iter->next) {
539 UiObjectMember *member = iter->member;
540 int idx = -1;
541
542 if (cast<UiObjectDefinition*>(member))
543 lastObjectDef = iter;
544 else if (UiArrayBinding *arrayBinding = cast<UiArrayBinding*>(member))
545 idx = propertyOrder.indexOf(toString(arrayBinding->qualifiedId));
546 else if (UiObjectBinding *objectBinding = cast<UiObjectBinding*>(member))
547 idx = propertyOrder.indexOf(toString(objectBinding->qualifiedId));
548 else if (UiScriptBinding *scriptBinding = cast<UiScriptBinding*>(member))
549 idx = propertyOrder.indexOf(toString(scriptBinding->qualifiedId));
550 else if (cast<UiPublicMember*>(member))
551 idx = propertyOrder.indexOf(QLatin1String("property"));
552
553 if (idx < objectDefinitionInsertionPoint)
554 lastNonObjectDef = iter;
555 }
556
557 if (lastObjectDef)
558 return lastObjectDef;
559 else
560 return lastNonObjectDef;
561 }
562
563 UiObjectMemberList *QMLRewriter::searchMemberToInsertAfter(UiObjectMemberList *members, const QString &propertyName, const QStringList &propertyOrder)
564 {
565 if (!members)
566 return nullptr; // empty members
567
568 QHash<QString, UiObjectMemberList *> orderedMembers;
569
570 for (UiObjectMemberList *iter = members; iter; iter = iter->next) {
571 UiObjectMember *member = iter->member;
572
573 if (UiArrayBinding *arrayBinding = cast<UiArrayBinding*>(member))
574 orderedMembers[toString(arrayBinding->qualifiedId)] = iter;
575 else if (UiObjectBinding *objectBinding = cast<UiObjectBinding*>(member))
576 orderedMembers[toString(objectBinding->qualifiedId)] = iter;
577 else if (cast<UiObjectDefinition*>(member))
578 orderedMembers[QString()] = iter;
579 else if (UiScriptBinding *scriptBinding = cast<UiScriptBinding*>(member))
580 orderedMembers[toString(scriptBinding->qualifiedId)] = iter;
581 else if (cast<UiPublicMember*>(member))
582 orderedMembers[QStringLiteral("property")] = iter;
583 }
584
585 int idx = propertyOrder.indexOf(propertyName);
586 if (idx == -1)
587 idx = propertyOrder.indexOf(QString());
588 if (idx == -1)
589 idx = propertyOrder.size() - 1;
590
591 for (; idx > 0; --idx) {
592 const QString prop = propertyOrder.at(idx - 1);
593 UiObjectMemberList *candidate = orderedMembers.value(prop, 0);
594 if (candidate != 0)
595 return candidate;
596 }
597
598 return nullptr;
599 }
600
601 #endif
602
appendToArrayBinding(UiArrayBinding * arrayBinding,const QString & content)603 void Rewriter::appendToArrayBinding(UiArrayBinding *arrayBinding,
604 const QString &content)
605 {
606 UiObjectMember *lastMember = nullptr;
607 for (UiArrayMemberList *iter = arrayBinding->members; iter; iter = iter->next)
608 if (iter->member)
609 lastMember = iter->member;
610
611 if (!lastMember)
612 return; // an array binding cannot be empty, so there will (or should) always be a last member.
613
614 const int insertionPoint = lastMember->lastSourceLocation().end();
615
616 m_changeSet->insert(insertionPoint, QLatin1String(",\n") + content);
617 }
618
addObject(UiObjectInitializer * ast,const QString & content)619 Rewriter::Range Rewriter::addObject(UiObjectInitializer *ast, const QString &content)
620 {
621 UiObjectMemberList *insertAfter = searchMemberToInsertAfter(ast->members, m_propertyOrder);
622 return addObject(ast, content, insertAfter);
623 }
624
addObject(UiObjectInitializer * ast,const QString & content,UiObjectMemberList * insertAfter)625 Rewriter::Range Rewriter::addObject(UiObjectInitializer *ast, const QString &content, UiObjectMemberList *insertAfter)
626 {
627 int insertionPoint;
628 QString textToInsert;
629 if (insertAfter && insertAfter->member) {
630 insertionPoint = insertAfter->member->lastSourceLocation().end();
631 textToInsert += QLatin1String("\n");
632 } else {
633 insertionPoint = ast->lbraceToken.end();
634 }
635
636 textToInsert += content;
637 m_changeSet->insert(insertionPoint, QLatin1String("\n") + textToInsert);
638
639 return {insertionPoint, insertionPoint};
640 }
641
addObject(UiArrayBinding * ast,const QString & content)642 Rewriter::Range Rewriter::addObject(UiArrayBinding *ast, const QString &content)
643 {
644 UiArrayMemberList *insertAfter = searchMemberToInsertAfter(ast->members, m_propertyOrder);
645 return addObject(ast, content, insertAfter);
646 }
647
addObject(UiArrayBinding * ast,const QString & content,UiArrayMemberList * insertAfter)648 Rewriter::Range Rewriter::addObject(UiArrayBinding *ast, const QString &content, UiArrayMemberList *insertAfter)
649 {
650 int insertionPoint;
651 QString textToInsert;
652 if (insertAfter && insertAfter->member) {
653 insertionPoint = insertAfter->member->lastSourceLocation().end();
654 textToInsert = QLatin1String(",\n") + content;
655 } else {
656 insertionPoint = ast->lbracketToken.end();
657 textToInsert += QLatin1String("\n") + content + QLatin1Char(',');
658 }
659
660 m_changeSet->insert(insertionPoint, textToInsert);
661
662 return {insertionPoint, insertionPoint};
663 }
664
removeObjectMember(UiObjectMember * member,UiObjectMember * parent)665 void Rewriter::removeObjectMember(UiObjectMember *member, UiObjectMember *parent)
666 {
667 int start = member->firstSourceLocation().offset;
668 int end = member->lastSourceLocation().end();
669
670 if (auto parentArray = cast<UiArrayBinding *>(parent)) {
671 extendToLeadingOrTrailingComma(parentArray, member, start, end);
672 } else {
673 if (auto parentObjectDefinition = cast<UiObjectDefinition *>(parent))
674 includeEmptyGroupedProperty(parentObjectDefinition, member, start, end);
675 includeSurroundingWhitespace(m_originalText, start, end);
676 }
677
678 includeLeadingEmptyLine(m_originalText, start);
679 m_changeSet->remove(start, end);
680 }
681
extendToLeadingOrTrailingComma(UiArrayBinding * parentArray,UiObjectMember * member,int & start,int & end) const682 void Rewriter::extendToLeadingOrTrailingComma(UiArrayBinding *parentArray,
683 UiObjectMember *member,
684 int &start,
685 int &end) const
686 {
687 UiArrayMemberList *currentMember = nullptr;
688 for (UiArrayMemberList *it = parentArray->members; it; it = it->next) {
689 if (it->member == member) {
690 currentMember = it;
691 break;
692 }
693 }
694
695 if (!currentMember)
696 return;
697
698 if (currentMember->commaToken.isValid()) {
699 // leading comma
700 start = currentMember->commaToken.offset;
701 if (includeSurroundingWhitespace(m_originalText, start, end))
702 --end;
703 } else if (currentMember->next && currentMember->next->commaToken.isValid()) {
704 // trailing comma
705 end = currentMember->next->commaToken.end();
706 includeSurroundingWhitespace(m_originalText, start, end);
707 } else {
708 // array with 1 element, so remove the complete binding
709 start = parentArray->firstSourceLocation().offset;
710 end = parentArray->lastSourceLocation().end();
711 includeSurroundingWhitespace(m_originalText, start, end);
712 }
713 }
714
715 } // namespace QbsQmlJS
716