summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorChristian Kandeler <christian.kandeler@digia.com>2013-11-14 11:11:22 +0100
committerChristian Kandeler <christian.kandeler@digia.com>2013-11-14 13:11:22 +0100
commit8c0f155a98f0cee77422577a06329d8b7f32ce1b (patch)
treeccedd829b59c1f1f7578854934742eff53688c1f
parent9e89d3a109a3d82c80f2d51552849f980bc32428 (diff)
downloadqbs-8c0f155a98f0cee77422577a06329d8b7f32ce1b.tar.gz
Import code for rewriting QML files.
This comes from Qt Creator and is needed for API functionality such as adding files to a project. Change-Id: Ia5ecef1d5a5d937cf3fbf27448abbce051e977de Reviewed-by: Joerg Bornemann <joerg.bornemann@digia.com>
-rw-r--r--src/lib/api/api.pri4
-rw-r--r--src/lib/api/changeset.cpp387
-rw-r--r--src/lib/api/changeset.h130
-rw-r--r--src/lib/api/qmljsrewriter.cpp718
-rw-r--r--src/lib/api/qmljsrewriter.h120
-rw-r--r--src/lib/lib.pro2
-rw-r--r--src/lib/lib.qbs6
7 files changed, 1365 insertions, 2 deletions
diff --git a/src/lib/api/api.pri b/src/lib/api/api.pri
index 61632356b..329675b37 100644
--- a/src/lib/api/api.pri
+++ b/src/lib/api/api.pri
@@ -1,4 +1,5 @@
HEADERS += \
+ $$PWD/changeset.h \
$$PWD/internaljobs.h \
$$PWD/projectdata.h \
$$PWD/runenvironment.h \
@@ -6,13 +7,16 @@ HEADERS += \
$$PWD/project.h \
$$PWD/propertymap_p.h \
$$PWD/projectdata_p.h
+ $$PWD/qmljsrewriter.h
SOURCES += \
+ $$PWD/changeset.cpp \
$$PWD/internaljobs.cpp \
$$PWD/runenvironment.cpp \
$$PWD/projectdata.cpp \
$$PWD/jobs.cpp \
$$PWD/project.cpp
+ $$PWD/qmljsrewriter.cpp
!qbs_no_dev_install {
api_headers.files = $$PWD/projectdata.h $$PWD/runenvironment.h $$PWD/jobs.h $$PWD/project.h
diff --git a/src/lib/api/changeset.cpp b/src/lib/api/changeset.cpp
new file mode 100644
index 000000000..6a7fbcc6b
--- /dev/null
+++ b/src/lib/api/changeset.cpp
@@ -0,0 +1,387 @@
+/****************************************************************************
+**
+** Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies).
+** Contact: http://www.qt-project.org/legal
+**
+** This file is part of the Qt Build Suite.
+**
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and Digia. For licensing terms and
+** conditions see http://qt.digia.com/licensing. For further information
+** use the contact form at http://qt.digia.com/contact-us.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 2.1 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 2.1 requirements
+** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** In addition, as a special exception, Digia gives you certain additional
+** rights. These rights are described in the Digia Qt LGPL Exception
+** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
+**
+****************************************************************************/
+
+#include "changeset.h"
+
+#include <QTextCursor>
+
+namespace QbsQmlJS {
+
+ChangeSet::ChangeSet()
+ : m_string(0), m_cursor(0), m_error(false)
+{
+}
+
+ChangeSet::ChangeSet(const QList<EditOp> &operations)
+ : m_string(0), m_cursor(0), m_operationList(operations), m_error(false)
+{
+}
+
+static bool overlaps(int posA, int lengthA, int posB, int lengthB) {
+ if (lengthB > 0) {
+ return
+ // right edge of B contained in A
+ (posA < posB + lengthB && posA + lengthA >= posB + lengthB)
+ // left edge of B contained in A
+ || (posA <= posB && posA + lengthA > posB)
+ // A contained in B
+ || (posB < posA && posB + lengthB > posA + lengthA);
+ } else {
+ return (posB > posA && posB < posA + lengthA);
+ }
+}
+
+bool ChangeSet::hasOverlap(int pos, int length)
+{
+ QListIterator<EditOp> i(m_operationList);
+ while (i.hasNext()) {
+ const EditOp &cmd = i.next();
+
+ switch (cmd.type) {
+ case EditOp::Replace:
+ if (overlaps(pos, length, cmd.pos1, cmd.length1))
+ return true;
+ break;
+
+ case EditOp::Move:
+ if (overlaps(pos, length, cmd.pos1, cmd.length1))
+ return true;
+ if (cmd.pos2 > pos && cmd.pos2 < pos + length)
+ return true;
+ break;
+
+ case EditOp::Insert:
+ if (cmd.pos1 > pos && cmd.pos1 < pos + length)
+ return true;
+ break;
+
+ case EditOp::Remove:
+ if (overlaps(pos, length, cmd.pos1, cmd.length1))
+ return true;
+ break;
+
+ case EditOp::Flip:
+ if (overlaps(pos, length, cmd.pos1, cmd.length1))
+ return true;
+ if (overlaps(pos, length, cmd.pos2, cmd.length2))
+ return true;
+ break;
+
+ case EditOp::Copy:
+ if (overlaps(pos, length, cmd.pos1, cmd.length1))
+ return true;
+ if (cmd.pos2 > pos && cmd.pos2 < pos + length)
+ return true;
+ break;
+
+ case EditOp::Unset:
+ break;
+ }
+ }
+
+ return false;
+}
+
+bool ChangeSet::isEmpty() const
+{
+ return m_operationList.isEmpty();
+}
+
+QList<ChangeSet::EditOp> ChangeSet::operationList() const
+{
+ return m_operationList;
+}
+
+void ChangeSet::clear()
+{
+ m_string = 0;
+ m_cursor = 0;
+ m_operationList.clear();
+ m_error = false;
+}
+
+bool ChangeSet::replace_helper(int pos, int length, const QString &replacement)
+{
+ if (hasOverlap(pos, length))
+ m_error = true;
+
+ EditOp cmd(EditOp::Replace);
+ cmd.pos1 = pos;
+ cmd.length1 = length;
+ cmd.text = replacement;
+ m_operationList += cmd;
+
+ return !m_error;
+}
+
+bool ChangeSet::move_helper(int pos, int length, int to)
+{
+ if (hasOverlap(pos, length)
+ || hasOverlap(to, 0)
+ || overlaps(pos, length, to, 0))
+ m_error = true;
+
+ EditOp cmd(EditOp::Move);
+ cmd.pos1 = pos;
+ cmd.length1 = length;
+ cmd.pos2 = to;
+ m_operationList += cmd;
+
+ return !m_error;
+}
+
+bool ChangeSet::insert(int pos, const QString &text)
+{
+ Q_ASSERT(pos >= 0);
+
+ if (hasOverlap(pos, 0))
+ m_error = true;
+
+ EditOp cmd(EditOp::Insert);
+ cmd.pos1 = pos;
+ cmd.text = text;
+ m_operationList += cmd;
+
+ return !m_error;
+}
+
+bool ChangeSet::replace(const Range &range, const QString &replacement)
+{ return replace(range.start, range.end, replacement); }
+
+bool ChangeSet::remove(const Range &range)
+{ return remove(range.start, range.end); }
+
+bool ChangeSet::move(const Range &range, int to)
+{ return move(range.start, range.end, to); }
+
+bool ChangeSet::flip(const Range &range1, const Range &range2)
+{ return flip(range1.start, range1.end, range2.start, range2.end); }
+
+bool ChangeSet::copy(const Range &range, int to)
+{ return copy(range.start, range.end, to); }
+
+bool ChangeSet::replace(int start, int end, const QString &replacement)
+{ return replace_helper(start, end - start, replacement); }
+
+bool ChangeSet::remove(int start, int end)
+{ return remove_helper(start, end - start); }
+
+bool ChangeSet::move(int start, int end, int to)
+{ return move_helper(start, end - start, to); }
+
+bool ChangeSet::flip(int start1, int end1, int start2, int end2)
+{ return flip_helper(start1, end1 - start1, start2, end2 - start2); }
+
+bool ChangeSet::copy(int start, int end, int to)
+{ return copy_helper(start, end - start, to); }
+
+bool ChangeSet::remove_helper(int pos, int length)
+{
+ if (hasOverlap(pos, length))
+ m_error = true;
+
+ EditOp cmd(EditOp::Remove);
+ cmd.pos1 = pos;
+ cmd.length1 = length;
+ m_operationList += cmd;
+
+ return !m_error;
+}
+
+bool ChangeSet::flip_helper(int pos1, int length1, int pos2, int length2)
+{
+ if (hasOverlap(pos1, length1)
+ || hasOverlap(pos2, length2)
+ || overlaps(pos1, length1, pos2, length2))
+ m_error = true;
+
+ EditOp cmd(EditOp::Flip);
+ cmd.pos1 = pos1;
+ cmd.length1 = length1;
+ cmd.pos2 = pos2;
+ cmd.length2 = length2;
+ m_operationList += cmd;
+
+ return !m_error;
+}
+
+bool ChangeSet::copy_helper(int pos, int length, int to)
+{
+ if (hasOverlap(pos, length)
+ || hasOverlap(to, 0)
+ || overlaps(pos, length, to, 0))
+ m_error = true;
+
+ EditOp cmd(EditOp::Copy);
+ cmd.pos1 = pos;
+ cmd.length1 = length;
+ cmd.pos2 = to;
+ m_operationList += cmd;
+
+ return !m_error;
+}
+
+void ChangeSet::doReplace(const EditOp &replace_helper, QList<EditOp> *replaceList)
+{
+ Q_ASSERT(replace_helper.type == EditOp::Replace);
+
+ {
+ QMutableListIterator<EditOp> i(*replaceList);
+ while (i.hasNext()) {
+ EditOp &c = i.next();
+ if (replace_helper.pos1 <= c.pos1)
+ c.pos1 += replace_helper.text.size();
+ if (replace_helper.pos1 < c.pos1)
+ c.pos1 -= replace_helper.length1;
+ }
+ }
+
+ if (m_string) {
+ m_string->replace(replace_helper.pos1, replace_helper.length1, replace_helper.text);
+ } else if (m_cursor) {
+ m_cursor->setPosition(replace_helper.pos1);
+ m_cursor->setPosition(replace_helper.pos1 + replace_helper.length1, QTextCursor::KeepAnchor);
+ m_cursor->insertText(replace_helper.text);
+ }
+}
+
+void ChangeSet::convertToReplace(const EditOp &op, QList<EditOp> *replaceList)
+{
+ EditOp replace1(EditOp::Replace);
+ EditOp replace2(EditOp::Replace);
+
+ switch (op.type) {
+ case EditOp::Replace:
+ replaceList->append(op);
+ break;
+
+ case EditOp::Move:
+ replace1.pos1 = op.pos1;
+ replace1.length1 = op.length1;
+ replaceList->append(replace1);
+
+ replace2.pos1 = op.pos2;
+ replace2.text = textAt(op.pos1, op.length1);
+ replaceList->append(replace2);
+ break;
+
+ case EditOp::Insert:
+ replace1.pos1 = op.pos1;
+ replace1.text = op.text;
+ replaceList->append(replace1);
+ break;
+
+ case EditOp::Remove:
+ replace1.pos1 = op.pos1;
+ replace1.length1 = op.length1;
+ replaceList->append(replace1);
+ break;
+
+ case EditOp::Flip:
+ replace1.pos1 = op.pos1;
+ replace1.length1 = op.length1;
+ replace1.text = textAt(op.pos2, op.length2);
+ replaceList->append(replace1);
+
+ replace2.pos1 = op.pos2;
+ replace2.length1 = op.length2;
+ replace2.text = textAt(op.pos1, op.length1);
+ replaceList->append(replace2);
+ break;
+
+ case EditOp::Copy:
+ replace1.pos1 = op.pos2;
+ replace1.text = textAt(op.pos1, op.length1);
+ replaceList->append(replace1);
+ break;
+
+ case EditOp::Unset:
+ break;
+ }
+}
+
+bool ChangeSet::hadErrors()
+{
+ return m_error;
+}
+
+void ChangeSet::apply(QString *s)
+{
+ m_string = s;
+ apply_helper();
+ m_string = 0;
+}
+
+void ChangeSet::apply(QTextCursor *textCursor)
+{
+ m_cursor = textCursor;
+ apply_helper();
+ m_cursor = 0;
+}
+
+QString ChangeSet::textAt(int pos, int length)
+{
+ if (m_string) {
+ return m_string->mid(pos, length);
+ } else if (m_cursor) {
+ m_cursor->setPosition(pos);
+ m_cursor->setPosition(pos + length, QTextCursor::KeepAnchor);
+ return m_cursor->selectedText();
+ }
+ return QString();
+}
+
+void ChangeSet::apply_helper()
+{
+ // convert all ops to replace
+ QList<EditOp> replaceList;
+ {
+ while (!m_operationList.isEmpty()) {
+ const EditOp cmd(m_operationList.first());
+ m_operationList.removeFirst();
+ convertToReplace(cmd, &replaceList);
+ }
+ }
+
+ // execute replaces
+ if (m_cursor)
+ m_cursor->beginEditBlock();
+
+ while (!replaceList.isEmpty()) {
+ const EditOp cmd(replaceList.first());
+ replaceList.removeFirst();
+ doReplace(cmd, &replaceList);
+ }
+
+ if (m_cursor)
+ m_cursor->endEditBlock();
+}
+
+} // namespace Internal {
+
diff --git a/src/lib/api/changeset.h b/src/lib/api/changeset.h
new file mode 100644
index 000000000..97f04fa7a
--- /dev/null
+++ b/src/lib/api/changeset.h
@@ -0,0 +1,130 @@
+/****************************************************************************
+**
+** Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies).
+** Contact: http://www.qt-project.org/legal
+**
+** This file is part of the Qt Build Suite.
+**
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and Digia. For licensing terms and
+** conditions see http://qt.digia.com/licensing. For further information
+** use the contact form at http://qt.digia.com/contact-us.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 2.1 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 2.1 requirements
+** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** In addition, as a special exception, Digia gives you certain additional
+** rights. These rights are described in the Digia Qt LGPL Exception
+** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
+**
+****************************************************************************/
+
+#ifndef QBS_CHANGESET_H
+#define QBS_CHANGESET_H
+
+#include <QString>
+#include <QList>
+
+QT_FORWARD_DECLARE_CLASS(QTextCursor)
+
+namespace QbsQmlJS {
+
+class ChangeSet
+{
+public:
+ struct EditOp {
+ enum Type
+ {
+ Unset,
+ Replace,
+ Move,
+ Insert,
+ Remove,
+ Flip,
+ Copy
+ };
+
+ EditOp(): type(Unset), pos1(0), pos2(0), length1(0), length2(0) {}
+ EditOp(Type t): type(t), pos1(0), pos2(0), length1(0), length2(0) {}
+
+ Type type;
+ int pos1;
+ int pos2;
+ int length1;
+ int length2;
+ QString text;
+ };
+
+ struct Range {
+ Range()
+ : start(0), end(0) {}
+
+ Range(int start, int end)
+ : start(start), end(end) {}
+
+ int start;
+ int end;
+ };
+
+public:
+ ChangeSet();
+ ChangeSet(const QList<EditOp> &operations);
+
+ bool isEmpty() const;
+
+ QList<EditOp> operationList() const;
+
+ void clear();
+
+ bool replace(const Range &range, const QString &replacement);
+ bool remove(const Range &range);
+ bool move(const Range &range, int to);
+ bool flip(const Range &range1, const Range &range2);
+ bool copy(const Range &range, int to);
+ bool replace(int start, int end, const QString &replacement);
+ bool remove(int start, int end);
+ bool move(int start, int end, int to);
+ bool flip(int start1, int end1, int start2, int end2);
+ bool copy(int start, int end, int to);
+ bool insert(int pos, const QString &text);
+
+ bool hadErrors();
+
+ void apply(QString *s);
+ void apply(QTextCursor *textCursor);
+
+private:
+ // length-based API.
+ bool replace_helper(int pos, int length, const QString &replacement);
+ bool move_helper(int pos, int length, int to);
+ bool remove_helper(int pos, int length);
+ bool flip_helper(int pos1, int length1, int pos2, int length2);
+ bool copy_helper(int pos, int length, int to);
+
+ bool hasOverlap(int pos, int length);
+ QString textAt(int pos, int length);
+
+ void doReplace(const EditOp &replace, QList<EditOp> *replaceList);
+ void convertToReplace(const EditOp &op, QList<EditOp> *replaceList);
+
+ void apply_helper();
+
+private:
+ QString *m_string;
+ QTextCursor *m_cursor;
+
+ QList<EditOp> m_operationList;
+ bool m_error;
+};
+
+} // namespace QbsQmlJS
+
+#endif // Include guard.
diff --git a/src/lib/api/qmljsrewriter.cpp b/src/lib/api/qmljsrewriter.cpp
new file mode 100644
index 000000000..adea2d866
--- /dev/null
+++ b/src/lib/api/qmljsrewriter.cpp
@@ -0,0 +1,718 @@
+/****************************************************************************
+**
+** Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies).
+** Contact: http://www.qt-project.org/legal
+**
+** This file is part of the Qt Build Suite.
+**
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and Digia. For licensing terms and
+** conditions see http://qt.digia.com/licensing. For further information
+** use the contact form at http://qt.digia.com/contact-us.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 2.1 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 2.1 requirements
+** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** In addition, as a special exception, Digia gives you certain additional
+** rights. These rights are described in the Digia Qt LGPL Exception
+** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
+**
+****************************************************************************/
+
+#include "qmljsrewriter.h"
+
+#include <parser/qmljsast_p.h>
+
+#include <QTextBlock>
+#include <QTextCursor>
+#include <QTextDocument>
+
+namespace QbsQmlJS {
+using namespace AST;
+
+static QString toString(UiQualifiedId *qualifiedId, QChar delimiter = QLatin1Char('.'))
+{
+ QString result;
+
+ for (UiQualifiedId *iter = qualifiedId; iter; iter = iter->next) {
+ if (iter != qualifiedId)
+ result += delimiter;
+
+ result += iter->name;
+ }
+
+ return result;
+}
+
+
+Rewriter::Rewriter(const QString &originalText,
+ ChangeSet *changeSet,
+ const QStringList &propertyOrder)
+ : m_originalText(originalText)
+ , m_changeSet(changeSet)
+ , m_propertyOrder(propertyOrder)
+{
+ Q_ASSERT(changeSet);
+}
+
+Rewriter::Range Rewriter::addBinding(AST::UiObjectInitializer *ast,
+ const QString &propertyName,
+ const QString &propertyValue,
+ BindingType bindingType)
+{
+ UiObjectMemberList *insertAfter = searchMemberToInsertAfter(ast->members,
+ propertyName,
+ m_propertyOrder);
+ return addBinding(ast, propertyName, propertyValue, bindingType, insertAfter);
+}
+
+Rewriter::Range Rewriter::addBinding(AST::UiObjectInitializer *ast,
+ const QString &propertyName,
+ const QString &propertyValue,
+ BindingType bindingType,
+ UiObjectMemberList *insertAfter)
+{
+ SourceLocation endOfPreviousMember;
+ SourceLocation startOfNextMember;
+
+ if (insertAfter == 0 || insertAfter->member == 0) {
+ // insert as first member
+ endOfPreviousMember = ast->lbraceToken;
+
+ if (ast->members && ast->members->member)
+ startOfNextMember = ast->members->member->firstSourceLocation();
+ else
+ startOfNextMember = ast->rbraceToken;
+ } else {
+ endOfPreviousMember = insertAfter->member->lastSourceLocation();
+
+ if (insertAfter->next && insertAfter->next->member)
+ startOfNextMember = insertAfter->next->member->firstSourceLocation();
+ else
+ startOfNextMember = ast->rbraceToken;
+ }
+ const bool isOneLiner = endOfPreviousMember.startLine == startOfNextMember.startLine;
+ bool needsPreceedingSemicolon = false;
+ bool needsTrailingSemicolon = false;
+
+ if (isOneLiner) {
+ if (insertAfter == 0) { // we're inserting after an lbrace
+ if (ast->members) { // we're inserting before a member (and not the rbrace)
+ needsTrailingSemicolon = bindingType == ScriptBinding;
+ }
+ } else { // we're inserting after a member, not after the lbrace
+ if (endOfPreviousMember.isValid()) { // there already is a semicolon after the previous member
+ if (insertAfter->next && insertAfter->next->member) { // and the after us there is a member, not an rbrace, so:
+ needsTrailingSemicolon = bindingType == ScriptBinding;
+ }
+ } else { // there is no semicolon after the previous member (probably because there is an rbrace after us/it, so:
+ needsPreceedingSemicolon = true;
+ }
+ }
+ }
+
+ QString newPropertyTemplate;
+ switch (bindingType) {
+ case ArrayBinding:
+ newPropertyTemplate = QLatin1String("%1: [\n%2\n]");
+ break;
+
+ case ObjectBinding:
+ newPropertyTemplate = QLatin1String("%1: %2");
+ break;
+
+ case ScriptBinding:
+ newPropertyTemplate = QLatin1String("%1: %2");
+ break;
+
+ default:
+ Q_ASSERT(!"unknown property type");
+ }
+
+ if (isOneLiner) {
+ if (needsPreceedingSemicolon)
+ newPropertyTemplate.prepend(QLatin1Char(';'));
+ newPropertyTemplate.prepend(QLatin1Char(' '));
+ if (needsTrailingSemicolon)
+ newPropertyTemplate.append(QLatin1Char(';'));
+ } else {
+ newPropertyTemplate.prepend(QLatin1Char('\n'));
+ }
+
+ m_changeSet->insert(endOfPreviousMember.end(),
+ newPropertyTemplate.arg(propertyName, propertyValue));
+
+ return Range(endOfPreviousMember.end(), endOfPreviousMember.end());
+}
+
+UiObjectMemberList *Rewriter::searchMemberToInsertAfter(UiObjectMemberList *members,
+ const QStringList &propertyOrder)
+{
+ const int objectDefinitionInsertionPoint = propertyOrder.indexOf(QString());
+
+ UiObjectMemberList *lastObjectDef = 0;
+ UiObjectMemberList *lastNonObjectDef = 0;
+
+ for (UiObjectMemberList *iter = members; iter; iter = iter->next) {
+ UiObjectMember *member = iter->member;
+ int idx = -1;
+
+ if (cast<UiObjectDefinition*>(member))
+ lastObjectDef = iter;
+ else if (UiArrayBinding *arrayBinding = cast<UiArrayBinding*>(member))
+ idx = propertyOrder.indexOf(toString(arrayBinding->qualifiedId));
+ else if (UiObjectBinding *objectBinding = cast<UiObjectBinding*>(member))
+ idx = propertyOrder.indexOf(toString(objectBinding->qualifiedId));
+ else if (UiScriptBinding *scriptBinding = cast<UiScriptBinding*>(member))
+ idx = propertyOrder.indexOf(toString(scriptBinding->qualifiedId));
+ else if (cast<UiPublicMember*>(member))
+ idx = propertyOrder.indexOf(QLatin1String("property"));
+
+ if (idx < objectDefinitionInsertionPoint)
+ lastNonObjectDef = iter;
+ }
+
+ if (lastObjectDef)
+ return lastObjectDef;
+ else
+ return lastNonObjectDef;
+}
+
+UiArrayMemberList *Rewriter::searchMemberToInsertAfter(UiArrayMemberList *members,
+ const QStringList &propertyOrder)
+{
+ const int objectDefinitionInsertionPoint = propertyOrder.indexOf(QString());
+
+ UiArrayMemberList *lastObjectDef = 0;
+ UiArrayMemberList *lastNonObjectDef = 0;
+
+ for (UiArrayMemberList *iter = members; iter; iter = iter->next) {
+ UiObjectMember *member = iter->member;
+ int idx = -1;
+
+ if (cast<UiObjectDefinition*>(member))
+ lastObjectDef = iter;
+ else if (UiArrayBinding *arrayBinding = cast<UiArrayBinding*>(member))
+ idx = propertyOrder.indexOf(toString(arrayBinding->qualifiedId));
+ else if (UiObjectBinding *objectBinding = cast<UiObjectBinding*>(member))
+ idx = propertyOrder.indexOf(toString(objectBinding->qualifiedId));
+ else if (UiScriptBinding *scriptBinding = cast<UiScriptBinding*>(member))
+ idx = propertyOrder.indexOf(toString(scriptBinding->qualifiedId));
+ else if (cast<UiPublicMember*>(member))
+ idx = propertyOrder.indexOf(QLatin1String("property"));
+
+ if (idx < objectDefinitionInsertionPoint)
+ lastNonObjectDef = iter;
+ }
+
+ if (lastObjectDef)
+ return lastObjectDef;
+ else
+ return lastNonObjectDef;
+}
+
+UiObjectMemberList *Rewriter::searchMemberToInsertAfter(UiObjectMemberList *members,
+ const QString &propertyName,
+ const QStringList &propertyOrder)
+{
+ if (!members)
+ return 0; // empty members
+
+ QHash<QString, UiObjectMemberList *> orderedMembers;
+
+ for (UiObjectMemberList *iter = members; iter; iter = iter->next) {
+ UiObjectMember *member = iter->member;
+
+ if (UiArrayBinding *arrayBinding = cast<UiArrayBinding*>(member))
+ orderedMembers[toString(arrayBinding->qualifiedId)] = iter;
+ else if (UiObjectBinding *objectBinding = cast<UiObjectBinding*>(member))
+ orderedMembers[toString(objectBinding->qualifiedId)] = iter;
+ else if (cast<UiObjectDefinition*>(member))
+ orderedMembers[QString::null] = iter;
+ else if (UiScriptBinding *scriptBinding = cast<UiScriptBinding*>(member))
+ orderedMembers[toString(scriptBinding->qualifiedId)] = iter;
+ else if (cast<UiPublicMember*>(member))
+ orderedMembers[QLatin1String("property")] = iter;
+ }
+
+ int idx = propertyOrder.indexOf(propertyName);
+ if (idx == -1)
+ idx = propertyOrder.indexOf(QString());
+ if (idx == -1)
+ idx = propertyOrder.size() - 1;
+
+ for (; idx > 0; --idx) {
+ const QString prop = propertyOrder.at(idx - 1);
+ UiObjectMemberList *candidate = orderedMembers.value(prop, 0);
+ if (candidate != 0)
+ return candidate;
+ }
+
+ return 0;
+}
+
+void Rewriter::changeBinding(UiObjectInitializer *ast,
+ const QString &propertyName,
+ const QString &newValue,
+ BindingType binding)
+{
+ QString prefix, suffix;
+ int dotIdx = propertyName.indexOf(QLatin1Char('.'));
+ if (dotIdx != -1) {
+ prefix = propertyName.left(dotIdx);
+ suffix = propertyName.mid(dotIdx + 1);
+ }
+
+ for (UiObjectMemberList *members = ast->members; members; members = members->next) {
+ UiObjectMember *member = members->member;
+
+ // for non-grouped properties:
+ if (isMatchingPropertyMember(propertyName, member)) {
+ switch (binding) {
+ case ArrayBinding:
+ insertIntoArray(cast<UiArrayBinding*>(member), newValue);
+ break;
+
+ case ObjectBinding:
+ replaceMemberValue(member, newValue, false);
+ break;
+
+ case ScriptBinding:
+ replaceMemberValue(member, newValue, nextMemberOnSameLine(members));
+ break;
+
+ default:
+ Q_ASSERT(!"Unhandled QmlRefactoring::PropertyType");
+ }
+
+ break;
+ // for grouped properties:
+ } else if (!prefix.isEmpty()) {
+ if (UiObjectDefinition *def = cast<UiObjectDefinition *>(member)) {
+ if (toString(def->qualifiedTypeNameId) == prefix)
+ changeBinding(def->initializer, suffix, newValue, binding);
+ }
+ }
+ }
+}
+
+void Rewriter::replaceMemberValue(UiObjectMember *propertyMember,
+ const QString &newValue,
+ bool needsSemicolon)
+{
+ QString replacement = newValue;
+ int startOffset = -1;
+ int endOffset = -1;
+ if (UiObjectBinding *objectBinding = AST::cast<UiObjectBinding *>(propertyMember)) {
+ startOffset = objectBinding->qualifiedTypeNameId->identifierToken.offset;
+ endOffset = objectBinding->initializer->rbraceToken.end();
+ } else if (UiScriptBinding *scriptBinding = AST::cast<UiScriptBinding *>(propertyMember)) {
+ startOffset = scriptBinding->statement->firstSourceLocation().offset;
+ endOffset = scriptBinding->statement->lastSourceLocation().end();
+ } else if (UiArrayBinding *arrayBinding = AST::cast<UiArrayBinding *>(propertyMember)) {
+ startOffset = arrayBinding->lbracketToken.offset;
+ endOffset = arrayBinding->rbracketToken.end();
+ } else if (UiPublicMember *publicMember = AST::cast<UiPublicMember*>(propertyMember)) {
+ if (publicMember->statement) {
+ startOffset = publicMember->statement->firstSourceLocation().offset;
+ if (publicMember->semicolonToken.isValid())
+ endOffset = publicMember->semicolonToken.end();
+ else
+ endOffset = publicMember->statement->lastSourceLocation().offset;
+ } else {
+ startOffset = publicMember->lastSourceLocation().end();
+ endOffset = startOffset;
+ if (publicMember->semicolonToken.isValid())
+ startOffset = publicMember->semicolonToken.offset;
+ replacement.prepend(QLatin1String(": "));
+ }
+ } else {
+ return;
+ }
+
+ if (needsSemicolon)
+ replacement += QLatin1Char(';');
+
+ m_changeSet->replace(startOffset, endOffset, replacement);
+}
+
+bool Rewriter::isMatchingPropertyMember(const QString &propertyName,
+ UiObjectMember *member)
+{
+ if (UiPublicMember *publicMember = cast<UiPublicMember*>(member))
+ return publicMember->name == propertyName;
+ else if (UiObjectBinding *objectBinding = cast<UiObjectBinding*>(member))
+ return toString(objectBinding->qualifiedId) == propertyName;
+ else if (UiScriptBinding *scriptBinding = cast<UiScriptBinding*>(member))
+ return toString(scriptBinding->qualifiedId) == propertyName;
+ else if (UiArrayBinding *arrayBinding = cast<UiArrayBinding*>(member))
+ return toString(arrayBinding->qualifiedId) == propertyName;
+ else
+ return false;
+}
+
+bool Rewriter::nextMemberOnSameLine(UiObjectMemberList *members)
+{
+ if (members && members->next && members->next->member)
+ return members->next->member->firstSourceLocation().startLine == members->member->lastSourceLocation().startLine;
+ else
+ return false;
+}
+
+void Rewriter::insertIntoArray(UiArrayBinding *ast, const QString &newValue)
+{
+ if (!ast)
+ return;
+
+ UiObjectMember *lastMember = 0;
+ for (UiArrayMemberList *iter = ast->members; iter; iter = iter->next) {
+ lastMember = iter->member;
+ }
+
+ if (!lastMember)
+ return;
+
+ const int insertionPoint = lastMember->lastSourceLocation().end();
+ m_changeSet->insert(insertionPoint, QLatin1String(",\n") + newValue);
+}
+
+void Rewriter::removeBindingByName(UiObjectInitializer *ast, const QString &propertyName)
+{
+ QString prefix;
+ int dotIdx = propertyName.indexOf(QLatin1Char('.'));
+ if (dotIdx != -1)
+ prefix = propertyName.left(dotIdx);
+
+ for (UiObjectMemberList *it = ast->members; it; it = it->next) {
+ UiObjectMember *member = it->member;
+
+ // run full name match (for ungrouped properties):
+ if (isMatchingPropertyMember(propertyName, member)) {
+ removeMember(member);
+ // check for grouped properties:
+ } else if (!prefix.isEmpty()) {
+ if (UiObjectDefinition *def = cast<UiObjectDefinition *>(member)) {
+ if (toString(def->qualifiedTypeNameId) == prefix)
+ removeGroupedProperty(def, propertyName);
+ }
+ }
+ }
+}
+
+void Rewriter::removeGroupedProperty(UiObjectDefinition *ast,
+ const QString &propertyName)
+{
+ int dotIdx = propertyName.indexOf(QLatin1Char('.'));
+ if (dotIdx == -1)
+ return;
+
+ const QString propName = propertyName.mid(dotIdx + 1);
+
+ UiObjectMember *wanted = 0;
+ unsigned memberCount = 0;
+ for (UiObjectMemberList *it = ast->initializer->members; it; it = it->next) {
+ ++memberCount;
+ UiObjectMember *member = it->member;
+
+ if (!wanted && isMatchingPropertyMember(propName, member))
+ wanted = member;
+ }
+
+ if (!wanted)
+ return;
+ if (memberCount == 1)
+ removeMember(ast);
+ else
+ removeMember(wanted);
+}
+
+void Rewriter::removeMember(UiObjectMember *member)
+{
+ int start = member->firstSourceLocation().offset;
+ int end = member->lastSourceLocation().end();
+
+ includeSurroundingWhitespace(m_originalText, start, end);
+
+ m_changeSet->remove(start, end);
+}
+
+bool Rewriter::includeSurroundingWhitespace(const QString &source, int &start, int &end)
+{
+ bool includeStartingWhitespace = true;
+ bool paragraphFound = false;
+ bool paragraphSkipped = false;
+
+ if (end >= 0) {
+ QChar c = source.at(end);
+
+ while (c.isSpace()) {
+ ++end;
+ if (c.unicode() == 10) {
+ paragraphFound = true;
+ paragraphSkipped = true;
+ break;
+ } else if (end == source.length()) {
+ break;
+ }
+
+ c = source.at(end);
+ }
+
+ includeStartingWhitespace = paragraphFound;
+ }
+
+ paragraphFound = false;
+ if (includeStartingWhitespace) {
+ while (start > 0) {
+ const QChar c = source.at(start - 1);
+
+ if (c.unicode() == 10) {
+ paragraphFound = true;
+ break;
+ }
+ if (!c.isSpace())
+ break;
+
+ --start;
+ }
+ }
+ if (!paragraphFound && paragraphSkipped) //keep the line ending
+ --end;
+
+ return paragraphFound;
+}
+
+void Rewriter::includeLeadingEmptyLine(const QString &source, int &start)
+{
+ QTextDocument doc(source);
+
+ if (start == 0)
+ return;
+
+ if (doc.characterAt(start - 1) != QChar::ParagraphSeparator)
+ return;
+
+ QTextCursor tc(&doc);
+ tc.setPosition(start);
+ const int blockNr = tc.blockNumber();
+ if (blockNr == 0)
+ return;
+
+ const QTextBlock prevBlock = tc.block().previous();
+ const QString trimmedPrevBlockText = prevBlock.text().trimmed();
+ if (!trimmedPrevBlockText.isEmpty())
+ return;
+
+ start = prevBlock.position();
+}
+
+void Rewriter::includeEmptyGroupedProperty(UiObjectDefinition *groupedProperty, UiObjectMember *memberToBeRemoved, int &start, int &end)
+{
+ if (groupedProperty->qualifiedTypeNameId && !groupedProperty->qualifiedTypeNameId->name.isEmpty()
+ && groupedProperty->qualifiedTypeNameId->name.at(0).isLower()) {
+ // grouped property
+ UiObjectMemberList *memberIter = groupedProperty->initializer->members;
+ while (memberIter) {
+ if (memberIter->member != memberToBeRemoved)
+ return;
+ memberIter = memberIter->next;
+ }
+ start = groupedProperty->firstSourceLocation().begin();
+ end = groupedProperty->lastSourceLocation().end();
+ }
+}
+
+#if 0
+UiObjectMemberList *QMLRewriter::searchMemberToInsertAfter(UiObjectMemberList *members, const QStringList &propertyOrder)
+{
+ const int objectDefinitionInsertionPoint = propertyOrder.indexOf(QString::null);
+
+ UiObjectMemberList *lastObjectDef = 0;
+ UiObjectMemberList *lastNonObjectDef = 0;
+
+ for (UiObjectMemberList *iter = members; iter; iter = iter->next) {
+ UiObjectMember *member = iter->member;
+ int idx = -1;
+
+ if (cast<UiObjectDefinition*>(member))
+ lastObjectDef = iter;
+ else if (UiArrayBinding *arrayBinding = cast<UiArrayBinding*>(member))
+ idx = propertyOrder.indexOf(toString(arrayBinding->qualifiedId));
+ else if (UiObjectBinding *objectBinding = cast<UiObjectBinding*>(member))
+ idx = propertyOrder.indexOf(toString(objectBinding->qualifiedId));
+ else if (UiScriptBinding *scriptBinding = cast<UiScriptBinding*>(member))
+ idx = propertyOrder.indexOf(toString(scriptBinding->qualifiedId));
+ else if (cast<UiPublicMember*>(member))
+ idx = propertyOrder.indexOf(QLatin1String("property"));
+
+ if (idx < objectDefinitionInsertionPoint)
+ lastNonObjectDef = iter;
+ }
+
+ if (lastObjectDef)
+ return lastObjectDef;
+ else
+ return lastNonObjectDef;
+}
+
+UiObjectMemberList *QMLRewriter::searchMemberToInsertAfter(UiObjectMemberList *members, const QString &propertyName, const QStringList &propertyOrder)
+{
+ if (!members)
+ return 0; // empty members
+
+ QHash<QString, UiObjectMemberList *> orderedMembers;
+
+ for (UiObjectMemberList *iter = members; iter; iter = iter->next) {
+ UiObjectMember *member = iter->member;
+
+ if (UiArrayBinding *arrayBinding = cast<UiArrayBinding*>(member))
+ orderedMembers[toString(arrayBinding->qualifiedId)] = iter;
+ else if (UiObjectBinding *objectBinding = cast<UiObjectBinding*>(member))
+ orderedMembers[toString(objectBinding->qualifiedId)] = iter;
+ else if (cast<UiObjectDefinition*>(member))
+ orderedMembers[QString::null] = iter;
+ else if (UiScriptBinding *scriptBinding = cast<UiScriptBinding*>(member))
+ orderedMembers[toString(scriptBinding->qualifiedId)] = iter;
+ else if (cast<UiPublicMember*>(member))
+ orderedMembers[QLatin1String("property")] = iter;
+ }
+
+ int idx = propertyOrder.indexOf(propertyName);
+ if (idx == -1)
+ idx = propertyOrder.indexOf(QString());
+ if (idx == -1)
+ idx = propertyOrder.size() - 1;
+
+ for (; idx > 0; --idx) {
+ const QString prop = propertyOrder.at(idx - 1);
+ UiObjectMemberList *candidate = orderedMembers.value(prop, 0);
+ if (candidate != 0)
+ return candidate;
+ }
+
+ return 0;
+}
+
+#endif
+
+void Rewriter::appendToArrayBinding(UiArrayBinding *arrayBinding,
+ const QString &content)
+{
+ UiObjectMember *lastMember = 0;
+ for (UiArrayMemberList *iter = arrayBinding->members; iter; iter = iter->next)
+ if (iter->member)
+ lastMember = iter->member;
+
+ if (!lastMember)
+ return; // an array binding cannot be empty, so there will (or should) always be a last member.
+
+ const int insertionPoint = lastMember->lastSourceLocation().end();
+
+ m_changeSet->insert(insertionPoint, QLatin1String(",\n") + content);
+}
+
+Rewriter::Range Rewriter::addObject(UiObjectInitializer *ast, const QString &content)
+{
+ UiObjectMemberList *insertAfter = searchMemberToInsertAfter(ast->members, m_propertyOrder);
+ return addObject(ast, content, insertAfter);
+}
+
+Rewriter::Range Rewriter::addObject(UiObjectInitializer *ast, const QString &content, UiObjectMemberList *insertAfter)
+{
+ int insertionPoint;
+ QString textToInsert;
+ if (insertAfter && insertAfter->member) {
+ insertionPoint = insertAfter->member->lastSourceLocation().end();
+ textToInsert += QLatin1String("\n");
+ } else {
+ insertionPoint = ast->lbraceToken.end();
+ }
+
+ textToInsert += content;
+ m_changeSet->insert(insertionPoint, QLatin1String("\n") + textToInsert);
+
+ return Range(insertionPoint, insertionPoint);
+}
+
+Rewriter::Range Rewriter::addObject(UiArrayBinding *ast, const QString &content)
+{
+ UiArrayMemberList *insertAfter = searchMemberToInsertAfter(ast->members, m_propertyOrder);
+ return addObject(ast, content, insertAfter);
+}
+
+Rewriter::Range Rewriter::addObject(UiArrayBinding *ast, const QString &content, UiArrayMemberList *insertAfter)
+{
+ int insertionPoint;
+ QString textToInsert;
+ if (insertAfter && insertAfter->member) {
+ insertionPoint = insertAfter->member->lastSourceLocation().end();
+ textToInsert = QLatin1String(",\n") + content;
+ } else {
+ insertionPoint = ast->lbracketToken.end();
+ textToInsert += QLatin1String("\n") + content + QLatin1Char(',');
+ }
+
+ m_changeSet->insert(insertionPoint, textToInsert);
+
+ return Range(insertionPoint, insertionPoint);
+}
+
+void Rewriter::removeObjectMember(UiObjectMember *member, UiObjectMember *parent)
+{
+ int start = member->firstSourceLocation().offset;
+ int end = member->lastSourceLocation().end();
+
+ if (UiArrayBinding *parentArray = cast<UiArrayBinding *>(parent)) {
+ extendToLeadingOrTrailingComma(parentArray, member, start, end);
+ } else {
+ if (UiObjectDefinition *parentObjectDefinition = cast<UiObjectDefinition *>(parent))
+ includeEmptyGroupedProperty(parentObjectDefinition, member, start, end);
+ includeSurroundingWhitespace(m_originalText, start, end);
+ }
+
+ includeLeadingEmptyLine(m_originalText, start);
+ m_changeSet->remove(start, end);
+}
+
+void Rewriter::extendToLeadingOrTrailingComma(UiArrayBinding *parentArray,
+ UiObjectMember *member,
+ int &start,
+ int &end) const
+{
+ UiArrayMemberList *currentMember = 0;
+ for (UiArrayMemberList *it = parentArray->members; it; it = it->next) {
+ if (it->member == member) {
+ currentMember = it;
+ break;
+ }
+ }
+
+ if (!currentMember)
+ return;
+
+ if (currentMember->commaToken.isValid()) {
+ // leading comma
+ start = currentMember->commaToken.offset;
+ if (includeSurroundingWhitespace(m_originalText, start, end))
+ --end;
+ } else if (currentMember->next && currentMember->next->commaToken.isValid()) {
+ // trailing comma
+ end = currentMember->next->commaToken.end();
+ includeSurroundingWhitespace(m_originalText, start, end);
+ } else {
+ // array with 1 element, so remove the complete binding
+ start = parentArray->firstSourceLocation().offset;
+ end = parentArray->lastSourceLocation().end();
+ includeSurroundingWhitespace(m_originalText, start, end);
+ }
+}
+
+} // namespace QbsQmlJS
diff --git a/src/lib/api/qmljsrewriter.h b/src/lib/api/qmljsrewriter.h
new file mode 100644
index 000000000..dcdb28c28
--- /dev/null
+++ b/src/lib/api/qmljsrewriter.h
@@ -0,0 +1,120 @@
+/****************************************************************************
+**
+** Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies).
+** Contact: http://www.qt-project.org/legal
+**
+** This file is part of the Qt Build Suite.
+**
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and Digia. For licensing terms and
+** conditions see http://qt.digia.com/licensing. For further information
+** use the contact form at http://qt.digia.com/contact-us.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 2.1 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 2.1 requirements
+** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** In addition, as a special exception, Digia gives you certain additional
+** rights. These rights are described in the Digia Qt LGPL Exception
+** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
+**
+****************************************************************************/
+
+#ifndef QMLJSREWRITER_H
+#define QMLJSREWRITER_H
+
+#include "changeset.h"
+
+#include <parser/qmljsastfwd_p.h>
+
+#include <QStringList>
+
+namespace QbsQmlJS {
+
+class Rewriter
+{
+public:
+ enum BindingType {
+ ScriptBinding,
+ ObjectBinding,
+ ArrayBinding
+ };
+
+ typedef ChangeSet::Range Range;
+
+public:
+ Rewriter(const QString &originalText,
+ ChangeSet *changeSet,
+ const QStringList &propertyOrder);
+
+ Range addBinding(AST::UiObjectInitializer *ast,
+ const QString &propertyName,
+ const QString &propertyValue,
+ BindingType bindingType);
+
+ Range addBinding(AST::UiObjectInitializer *ast,
+ const QString &propertyName,
+ const QString &propertyValue,
+ BindingType bindingType,
+ AST::UiObjectMemberList *insertAfter);
+
+ void changeBinding(AST::UiObjectInitializer *ast,
+ const QString &propertyName,
+ const QString &newValue,
+ BindingType binding);
+
+ void removeBindingByName(AST::UiObjectInitializer *ast, const QString &propertyName);
+
+ void appendToArrayBinding(AST::UiArrayBinding *arrayBinding,
+ const QString &content);
+
+ Range addObject(AST::UiObjectInitializer *ast, const QString &content);
+ Range addObject(AST::UiObjectInitializer *ast, const QString &content, AST::UiObjectMemberList *insertAfter);
+ Range addObject(AST::UiArrayBinding *ast, const QString &content);
+ Range addObject(AST::UiArrayBinding *ast, const QString &content, AST::UiArrayMemberList *insertAfter);
+
+ void removeObjectMember(AST::UiObjectMember *member, AST::UiObjectMember *parent);
+
+ static AST::UiObjectMemberList *searchMemberToInsertAfter(AST::UiObjectMemberList *members, const QStringList &propertyOrder);
+ static AST::UiArrayMemberList *searchMemberToInsertAfter(AST::UiArrayMemberList *members, const QStringList &propertyOrder);
+ static AST::UiObjectMemberList *searchMemberToInsertAfter(AST::UiObjectMemberList *members, const QString &propertyName, const QStringList &propertyOrder);
+
+ static bool includeSurroundingWhitespace(const QString &source, int &start, int &end);
+ static void includeLeadingEmptyLine(const QString &source, int &start);
+ static void includeEmptyGroupedProperty(AST::UiObjectDefinition *groupedProperty, AST::UiObjectMember *memberToBeRemoved, int &start, int &end);
+
+private:
+ void replaceMemberValue(AST::UiObjectMember *propertyMember,
+ const QString &newValue,
+ bool needsSemicolon);
+ static bool isMatchingPropertyMember(const QString &propertyName,
+ AST::UiObjectMember *member);
+ static bool nextMemberOnSameLine(AST::UiObjectMemberList *members);
+
+ void insertIntoArray(AST::UiArrayBinding* ast, const QString &newValue);
+
+ void removeMember(AST::UiObjectMember *member);
+ void removeGroupedProperty(AST::UiObjectDefinition *ast,
+ const QString &propertyName);
+
+ void extendToLeadingOrTrailingComma(AST::UiArrayBinding *parentArray,
+ AST::UiObjectMember *member,
+ int &start,
+ int &end) const;
+
+private:
+ QString m_originalText;
+ ChangeSet *m_changeSet;
+ const QStringList m_propertyOrder;
+};
+
+} // namespace QbsQmlJS
+
+#endif // QMLJSREWRITER_H
diff --git a/src/lib/lib.pro b/src/lib/lib.pro
index 14c24a083..27dee1c39 100644
--- a/src/lib/lib.pro
+++ b/src/lib/lib.pro
@@ -1,4 +1,4 @@
-QT = core script
+QT = core script gui
all_tests:QT += testlib
TEMPLATE = lib
diff --git a/src/lib/lib.qbs b/src/lib/lib.qbs
index e30f142e9..c45997744 100644
--- a/src/lib/lib.qbs
+++ b/src/lib/lib.qbs
@@ -3,7 +3,7 @@ import "../../version.js" as Version
Product {
Depends { name: "cpp" }
- Depends { name: "Qt"; submodules: ["core", "script", "xml"] }
+ Depends { name: "Qt"; submodules: ["core", "gui", "script", "xml"] }
Depends { condition: project.enableUnitTests; name: "Qt.test" }
name: "qbscore"
version: Version.qbsVersion()
@@ -41,12 +41,16 @@ Product {
name: "api"
prefix: name + '/'
files: [
+ "changeset.cpp",
+ "changeset.h",
"internaljobs.cpp",
"internaljobs.h",
"jobs.cpp",
"project.cpp",
"projectdata.cpp",
"projectdata_p.h",
+ "qmljsrewriter.cpp",
+ "qmljsrewriter.h",
"propertymap_p.h",
"runenvironment.cpp",
]