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