aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorFawzi Mohamed <fawzi.mohamed@qt.io>2021-03-23 12:04:19 +0100
committerFawzi Mohamed <fawzi.mohamed@qt.io>2021-06-05 00:07:38 +0200
commit6cf15ad4e359223bb19c997f0dd279f9aea842d7 (patch)
treed29f61d703771d5f16795e3535497aa71068f071
parent52d61e705ee606f3b673c757bdf253bdf6134a3b (diff)
qmldom: compare and dump to file, domtool
* qqmldomfieldfilter: ignore fields or types in dump or comparison * qqmldomcompare: compare DomItem * qqmldomfilewriter: safely write to a file (keep backups, avoid overwrite before success is confirmed, avoid overwrite if equal) * qmldomtool: command line tool to dump dom Change-Id: If4fbff7dff70d3a780293ac8b458f674e8f91591 Reviewed-by: Fabian Kosmale <fabian.kosmale@qt.io>
-rw-r--r--src/qmldom/CMakeLists.txt3
-rw-r--r--src/qmldom/qqmldomcompare.cpp259
-rw-r--r--src/qmldom/qqmldomcompare_p.h106
-rw-r--r--src/qmldom/qqmldomfieldfilter.cpp239
-rw-r--r--src/qmldom/qqmldomfieldfilter_p.h100
-rw-r--r--src/qmldom/qqmldomfilewriter.cpp168
-rw-r--r--src/qmldom/qqmldomfilewriter_p.h98
-rw-r--r--src/qmldom/qqmldomitem.cpp29
-rw-r--r--src/qmldom/qqmldomitem_p.h12
-rw-r--r--tests/auto/qmldom/CMakeLists.txt1
-rw-r--r--tests/auto/qmldom/combined/CMakeLists.txt1
-rw-r--r--tests/auto/qmldom/combined/tst_dom_all.cpp6
-rw-r--r--tests/auto/qmldom/domdata/dommerging/test1.qml12
-rw-r--r--tests/auto/qmldom/merging/CMakeLists.txt29
-rw-r--r--tests/auto/qmldom/merging/tst_dommerging.cpp40
-rw-r--r--tests/auto/qmldom/merging/tst_dommerging.h143
-rw-r--r--tools/CMakeLists.txt1
-rw-r--r--tools/qmldom/CMakeLists.txt15
-rw-r--r--tools/qmldom/qmldomtool.cpp228
19 files changed, 1489 insertions, 1 deletions
diff --git a/src/qmldom/CMakeLists.txt b/src/qmldom/CMakeLists.txt
index 2c36f74b64..1ec6f55172 100644
--- a/src/qmldom/CMakeLists.txt
+++ b/src/qmldom/CMakeLists.txt
@@ -15,10 +15,13 @@ qt_internal_add_module(QmlDom
qqmldomastdumper.cpp qqmldomastdumper_p.h
qqmldomattachedinfo.cpp qqmldomattachedinfo_p.h
qqmldomcomments.cpp qqmldomcomments_p.h
+ qqmldomcompare.cpp qqmldomcompare_p.h
qqmldomconstants_p.h
qqmldomelements.cpp qqmldomelements_p.h
qqmldomerrormessage.cpp qqmldomerrormessage_p.h
qqmldomexternalitems.cpp qqmldomexternalitems_p.h
+ qqmldomfieldfilter.cpp qqmldomfieldfilter_p.h
+ qqmldomfilewriter.cpp qqmldomfilewriter_p.h
qqmldomfunctionref_p.h
qqmldomitem.cpp qqmldomitem_p.h
qqmldommock.cpp qqmldommock_p.h
diff --git a/src/qmldom/qqmldomcompare.cpp b/src/qmldom/qqmldomcompare.cpp
new file mode 100644
index 0000000000..feb4fd5e33
--- /dev/null
+++ b/src/qmldom/qqmldomcompare.cpp
@@ -0,0 +1,259 @@
+/****************************************************************************
+**
+** Copyright (C) 2020 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the QtQml module of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** 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 The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/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 3 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL3 included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 3 requirements
+** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 2.0 or (at your option) the GNU General
+** Public license version 3 or any later version approved by the KDE Free
+** Qt Foundation. The licenses are as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-2.0.html and
+** https://www.gnu.org/licenses/gpl-3.0.html.
+**
+** $QT_END_LICENSE$
+**/
+
+#include "qqmldomcompare_p.h"
+#include "QtCore/qglobal.h"
+
+QT_BEGIN_NAMESPACE
+
+namespace QQmlJS {
+namespace Dom {
+
+bool domCompare(DomItem &i1, DomItem &i2, function_ref<bool(Path, DomItem &, DomItem &)> change,
+ function_ref<bool(DomItem &, const PathEls::PathComponent &, DomItem &)> filter,
+ Path basePath)
+{
+ DomKind k1 = i1.domKind();
+ DomKind k2 = i2.domKind();
+ if (k1 != k2) {
+ if (!change(basePath, i1, i2))
+ return false;
+ } else {
+ switch (k1) {
+ case DomKind::Empty:
+ return true;
+ case DomKind::Object: {
+ QStringList f1 = i1.fields();
+ QStringList f2 = i2.fields();
+ f1.sort();
+ f2.sort();
+ qsizetype it1 = 0;
+ qsizetype it2 = 0;
+ while (it1 != f1.size() || it2 != f2.size()) {
+ QString k1, k2;
+ DomItem el1, el2;
+ bool hasK1 = it1 != f1.size();
+ bool filt1 = hasK1;
+ if (hasK1) {
+ k1 = f1.at(it1);
+ el1 = i1.field(k1);
+ filt1 = i1.isCanonicalChild(el1) && filter(i1, PathEls::Field(k1), el1);
+ }
+ bool hasK2 = it2 != f2.size();
+ bool filt2 = hasK2;
+ if (hasK2) {
+ k2 = f2.at(it2);
+ el2 = i2.field(k2);
+ filt2 = i2.isCanonicalChild(el2) && filter(i2, PathEls::Field(k2), el2);
+ }
+ // continue when filtering out
+ if (hasK1 && !filt1) {
+ ++it1;
+ if (hasK2 && !filt2)
+ ++it2;
+ continue;
+ } else if (hasK2 && !filt2) {
+ ++it2;
+ continue;
+ }
+ if (filt1 && filt2 && k1 == k2) {
+ if (!domCompare(el1, el2, change, filter, basePath.field(k1)))
+ return false;
+ ++it1;
+ ++it2;
+ } else if (!hasK1 || (hasK2 && k1 > k2)) {
+ if (!change(basePath.field(k1), DomItem::empty, el2))
+ return false;
+ ++it2;
+ } else {
+ if (!change(basePath.field(k1), el1, DomItem::empty))
+ return false;
+ ++it1;
+ }
+ }
+ } break;
+ case DomKind::Map: {
+ QStringList f1 = i1.sortedKeys();
+ QStringList f2 = i2.sortedKeys();
+ qsizetype it1 = 0;
+ qsizetype it2 = 0;
+ while (it1 != f1.size() || it2 != f2.size()) {
+ QString k1, k2;
+ DomItem el1, el2;
+ bool hasK1 = it1 != f1.size();
+ bool filt1 = hasK1;
+ if (hasK1) {
+ k1 = f1.at(it1);
+ el1 = i1.key(k1);
+ filt1 = i1.isCanonicalChild(el1) && filter(i1, PathEls::Key(k1), el1);
+ }
+ bool hasK2 = it2 != f2.size();
+ bool filt2 = hasK2;
+ if (hasK2) {
+ k2 = f2.at(it2);
+ el2 = i2.key(k2);
+ filt2 = i2.isCanonicalChild(el2) && filter(i2, PathEls::Key(k2), el2);
+ }
+ // continue when filtering out
+ if (hasK1 && !filt1) {
+ ++it1;
+ if (hasK2 && !filt2)
+ ++it2;
+ continue;
+ } else if (hasK2 && !filt2) {
+ ++it2;
+ continue;
+ }
+ if (filt1 && filt2 && k1 == k2) {
+ if (!domCompare(el1, el2, change, filter, basePath.key(k1)))
+ return false;
+ ++it1;
+ ++it2;
+ } else if (!hasK1 || (hasK2 && k1 > k2)) {
+ if (!change(basePath.key(k1), DomItem::empty, el2))
+ return false;
+ ++it2;
+ } else {
+ if (!change(basePath.key(k1), el1, DomItem::empty))
+ return false;
+ ++it1;
+ }
+ }
+ } break;
+ case DomKind::List: {
+ // we could support smarter matching keeping filtering in account like map
+ // currently it is just a simple index by index comparison
+ index_type len1 = i1.indexes();
+ index_type len2 = i2.indexes();
+ if (len1 != len2)
+ return change(basePath, i1, i2);
+ for (index_type i = 0; i < len1; ++i) {
+ DomItem v1 = i1.index(i);
+ DomItem v2 = i2.index(i);
+ if (filter(i1, PathEls::Index(i), v1) && filter(i2, PathEls::Index(i), v2)) {
+ DomItem el1 = i1.index(i);
+ DomItem el2 = i2.index(i);
+ if (i1.isCanonicalChild(el1) && i2.isCanonicalChild(el2)
+ && !domCompare(el1, el2, change, filter, basePath.index(i)))
+ return false;
+ }
+ }
+ } break;
+ case DomKind::Value: {
+ QCborValue v1 = i1.value();
+ QCborValue v2 = i2.value();
+ if (v1 != v2)
+ return change(basePath, i1, i2);
+ } break;
+ }
+ }
+ return true;
+}
+
+QStringList
+domCompareStrList(DomItem &i1, DomItem &i2,
+ function_ref<bool(DomItem &, const PathEls::PathComponent &, DomItem &)> filter,
+ DomCompareStrList stopAtFirstDiff)
+{
+ QStringList res;
+ bool hasDiff = false;
+ domCompare(
+ i1, i2,
+ [&res, &hasDiff, stopAtFirstDiff](Path p, DomItem &j1, DomItem &j2) {
+ hasDiff = true;
+ if (!j1) {
+ res.append(QStringLiteral("- %1\n").arg(p.toString()));
+ } else if (!j2) {
+ res.append(QStringLiteral("+ %1\n").arg(p.toString()));
+ } else {
+ DomKind k1 = j1.domKind();
+ DomKind k2 = j2.domKind();
+ if (k1 != k2) {
+ res.append(
+ QStringLiteral("- %1 %2\n").arg(p.toString(), domKindToString(k1)));
+ res.append(
+ QStringLiteral("+ %1 %2\n").arg(p.toString(), domKindToString(k2)));
+ } else {
+ switch (k1) {
+ case DomKind::Empty:
+ case DomKind::Object:
+ case DomKind::Map:
+ Q_ASSERT(false);
+ break;
+ case DomKind::List: {
+ index_type len1 = j1.indexes();
+ index_type len2 = j2.indexes();
+ res.append(QStringLiteral("- %1 #%2\n").arg(p.toString()).arg(len1));
+ res.append(QStringLiteral("+ %1 #%2\n").arg(p.toString()).arg(len2));
+ } break;
+ case DomKind::Value: {
+ QCborValue v1 = j1.value();
+ QCborValue v2 = j2.value();
+ auto t1 = v1.type();
+ auto t2 = v2.type();
+ if (t1 != t2) {
+ res.append(QStringLiteral("- %1 type(%2)\n")
+ .arg(p.toString())
+ .arg(int(t1)));
+ res.append(QStringLiteral("+ %1 type(%2)\n")
+ .arg(p.toString())
+ .arg(int(t2)));
+ } else {
+ res.append(QStringLiteral("- %1 value(%2)\n")
+ .arg(p.toString())
+ .arg(j1.toString()));
+ res.append(QStringLiteral("+ %1 value(%2)\n")
+ .arg(p.toString())
+ .arg(j2.toString()));
+ }
+ } break;
+ }
+ }
+ }
+ return (stopAtFirstDiff == DomCompareStrList::AllDiffs);
+ },
+ filter);
+ if (hasDiff && res.isEmpty()) // should never happen
+ res.append(QStringLiteral(u"Had changes!"));
+ return res;
+}
+
+} // end namespace Dom
+} // end namespace QQmlJS
+
+QT_END_NAMESPACE
diff --git a/src/qmldom/qqmldomcompare_p.h b/src/qmldom/qqmldomcompare_p.h
new file mode 100644
index 0000000000..5200ae84dd
--- /dev/null
+++ b/src/qmldom/qqmldomcompare_p.h
@@ -0,0 +1,106 @@
+/****************************************************************************
+**
+** Copyright (C) 2020 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the QtQml module of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** 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 The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/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 3 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL3 included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 3 requirements
+** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 2.0 or (at your option) the GNU General
+** Public license version 3 or any later version approved by the KDE Free
+** Qt Foundation. The licenses are as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-2.0.html and
+** https://www.gnu.org/licenses/gpl-3.0.html.
+**
+** $QT_END_LICENSE$
+**/
+#ifndef QMLDOMCOMPARE_P_H
+#define QMLDOMCOMPARE_P_H
+
+//
+// W A R N I N G
+// -------------
+//
+// This file is not part of the Qt API. It exists purely as an
+// implementation detail. This header file may change from version to
+// version without notice, or even be removed.
+//
+// We mean it.
+//
+
+#include "qqmldom_global.h"
+#include "qqmldomitem_p.h"
+
+#include <memory>
+
+QT_BEGIN_NAMESPACE
+
+namespace QQmlJS {
+namespace Dom {
+
+bool domCompare(
+ DomItem &i1, DomItem &i2, function_ref<bool(Path, DomItem &, DomItem &)> change,
+ function_ref<bool(DomItem &, const PathEls::PathComponent &, DomItem &)> filter = noFilter,
+ Path p = Path());
+
+enum DomCompareStrList { FirstDiff, AllDiffs };
+
+QStringList domCompareStrList(
+ DomItem &i1, DomItem &i2,
+ function_ref<bool(DomItem &, const PathEls::PathComponent &, DomItem &)> filter = noFilter,
+ DomCompareStrList stopAtFirstDiff = DomCompareStrList::FirstDiff);
+
+inline QStringList domCompareStrList(
+ MutableDomItem &i1, DomItem &i2,
+ function_ref<bool(DomItem &, const PathEls::PathComponent &, DomItem &)> filter = noFilter,
+ DomCompareStrList stopAtFirstDiff = DomCompareStrList::FirstDiff)
+{
+ DomItem ii1 = i1.item();
+ return domCompareStrList(ii1, i2, filter, stopAtFirstDiff);
+}
+
+inline QStringList domCompareStrList(
+ DomItem &i1, MutableDomItem &i2,
+ function_ref<bool(DomItem &, const PathEls::PathComponent &, DomItem &)> filter = noFilter,
+ DomCompareStrList stopAtFirstDiff = DomCompareStrList::FirstDiff)
+{
+ DomItem ii2 = i2.item();
+ return domCompareStrList(i1, ii2, filter, stopAtFirstDiff);
+}
+
+inline QStringList domCompareStrList(
+ MutableDomItem &i1, MutableDomItem &i2,
+ function_ref<bool(DomItem &, const PathEls::PathComponent &, DomItem &)> filter = noFilter,
+ DomCompareStrList stopAtFirstDiff = DomCompareStrList::FirstDiff)
+{
+ DomItem ii1 = i1.item();
+ DomItem ii2 = i2.item();
+ return domCompareStrList(ii1, ii2, filter, stopAtFirstDiff);
+}
+
+} // end namespace Dom
+} // end namespace QQmlJS
+
+QT_END_NAMESPACE
+#endif // QMLDOMCOMPARE_P_H
diff --git a/src/qmldom/qqmldomfieldfilter.cpp b/src/qmldom/qqmldomfieldfilter.cpp
new file mode 100644
index 0000000000..3fa39b5b49
--- /dev/null
+++ b/src/qmldom/qqmldomfieldfilter.cpp
@@ -0,0 +1,239 @@
+/****************************************************************************
+**
+** Copyright (C) 2020 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the QtQml module of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** 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 The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/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 3 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL3 included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 3 requirements
+** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 2.0 or (at your option) the GNU General
+** Public license version 3 or any later version approved by the KDE Free
+** Qt Foundation. The licenses are as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-2.0.html and
+** https://www.gnu.org/licenses/gpl-3.0.html.
+**
+** $QT_END_LICENSE$
+**/
+
+#include "qqmldomfieldfilter_p.h"
+#include "QtCore/qglobal.h"
+
+QT_BEGIN_NAMESPACE
+
+namespace QQmlJS {
+namespace Dom {
+
+QString FieldFilter::describeFieldsFilter() const
+{
+ QString fieldFilterStr;
+ {
+ auto it = m_fieldFilterRemove.begin();
+ while (it != m_fieldFilterRemove.end()) {
+ if (!fieldFilterStr.isEmpty())
+ fieldFilterStr.append(u",");
+ fieldFilterStr.append(QLatin1String("-%1:%2").arg(it.key(), it.value()));
+ ++it;
+ }
+ }
+ {
+ auto it = m_fieldFilterAdd.begin();
+ while (it != m_fieldFilterAdd.end()) {
+ if (!fieldFilterStr.isEmpty())
+ fieldFilterStr.append(u",");
+ fieldFilterStr.append(QLatin1String("+%1:%2").arg(it.key(), it.value()));
+ ++it;
+ }
+ }
+ return fieldFilterStr;
+}
+
+bool FieldFilter::operator()(DomItem &obj, Path p, DomItem &i) const
+{
+ if (p)
+ return this->operator()(obj, p.component(0), i);
+ else
+ return this->operator()(obj, PathEls::Empty(), i);
+}
+
+bool FieldFilter::operator()(DomItem &base, const PathEls::PathComponent &c, DomItem &obj) const
+{
+ DomType baseK = base.internalKind();
+ if (c.kind() == Path::Kind::Field) {
+ DomType objK = obj.internalKind();
+ if (!m_filtredTypes.contains(baseK) && !m_filtredTypes.contains(objK)
+ && !m_filtredFields.contains(qHash(c.stringView())))
+ return m_filtredDefault;
+ QString typeStr = domTypeToString(baseK);
+ QList<QString> tVals = m_fieldFilterRemove.values(typeStr);
+ QString name = c.name();
+ if (tVals.contains(name))
+ return false;
+ if (tVals.contains(QString())
+ || m_fieldFilterRemove.values(domTypeToString(objK)).contains(QString())
+ || m_fieldFilterRemove.values(QString()).contains(name)) {
+ return m_fieldFilterAdd.values(typeStr).contains(name);
+ }
+ } else if (m_filtredTypes.contains(baseK)) {
+ QString typeStr = domTypeToString(baseK);
+ QList<QString> tVals = m_fieldFilterRemove.values(typeStr);
+ return !tVals.contains(QString());
+ }
+ return true;
+}
+
+bool FieldFilter::addFilter(QString fFields)
+{
+ for (QString fField : fFields.split(QLatin1Char(','))) {
+ QRegularExpression fieldRe(QRegularExpression::anchoredPattern(QStringLiteral(
+ uR"((?<op>[-+])?(?:(?<type>[a-zA-Z0-9_]*):)?(?<field>[a-zA-Z0-9_]+))")));
+ QRegularExpressionMatch m = fieldRe.match(fField);
+ if (m.hasMatch()) {
+ if (m.captured(u"op") == u"+") {
+ m_fieldFilterRemove.remove(m.captured(u"type"), m.captured(u"field"));
+ m_fieldFilterAdd.insert(m.captured(u"type"), m.captured(u"field"));
+ } else {
+ m_fieldFilterRemove.insert(m.captured(u"type"), m.captured(u"field"));
+ m_fieldFilterAdd.remove(m.captured(u"type"), m.captured(u"field"));
+ }
+ } else {
+ qCWarning(domLog) << "could not extract filter from" << fField;
+ return false;
+ }
+ }
+ return true;
+}
+
+FieldFilter FieldFilter::defaultFilter()
+{
+ QMultiMap<QString, QString> fieldFilterAdd { { QLatin1String("ScriptExpression"),
+ QLatin1String("code") } };
+ QMultiMap<QString, QString> fieldFilterRemove { { QString(), QLatin1String("code") },
+ { QString(), QLatin1String("propertyInfos") },
+ { QLatin1String("AttachedInfo"),
+ QLatin1String("parent") } };
+
+ return FieldFilter { fieldFilterAdd, fieldFilterRemove };
+}
+
+QQmlJS::Dom::FieldFilter QQmlJS::Dom::FieldFilter::noLocationFilter()
+{
+ QMultiMap<QString, QString> fieldFilterAdd {};
+ QMultiMap<QString, QString> fieldFilterRemove {
+ { QString(), QLatin1String("code") },
+ { QString(), QLatin1String("propertyInfos") },
+ { QString(), QLatin1String("fileLocationsTree") },
+ { QLatin1String("ScriptExpression"), QLatin1String("localOffset") },
+ { QLatin1String("ScriptExpression"), QLatin1String("preCode") },
+ { QLatin1String("ScriptExpression"), QLatin1String("postCode") },
+ { QLatin1String("AttachedInfo"), QLatin1String("parent") },
+ { QLatin1String("Reference"), QLatin1String("get") }
+ };
+ return FieldFilter { fieldFilterAdd, fieldFilterRemove };
+}
+
+FieldFilter FieldFilter::compareFilter()
+{
+ QMultiMap<QString, QString> fieldFilterAdd {};
+ QMultiMap<QString, QString> fieldFilterRemove {
+ { QString(), QLatin1String("propertyInfos") },
+ { QLatin1String("ScriptExpression"), QLatin1String("localOffset") },
+ { QLatin1String("FileLocations"), QLatin1String("regions") },
+ { QLatin1String("AttachedInfo"), QLatin1String("parent") },
+ { QLatin1String("Reference"), QLatin1String("get") }
+ };
+ return FieldFilter { fieldFilterAdd, fieldFilterRemove };
+}
+
+FieldFilter FieldFilter::compareNoCommentsFilter()
+{
+ QMultiMap<QString, QString> fieldFilterAdd {};
+ QMultiMap<QString, QString> fieldFilterRemove {
+ { QString(), QLatin1String("propertyInfos") },
+ { QLatin1String("FileLocations"), QLatin1String("regions") },
+ { QLatin1String("Reference"), QLatin1String("get") },
+ { QLatin1String(), QLatin1String("code") },
+ { QLatin1String("ScriptExpression"), QLatin1String("localOffset") },
+ { QLatin1String("AttachedInfo"), QLatin1String("parent") },
+ { QLatin1String(), QLatin1String("fileLocationsTree") },
+ { QLatin1String(), QLatin1String("preCode") },
+ { QLatin1String(), QLatin1String("postCode") },
+ { QLatin1String(), QLatin1String("comments") },
+ { QLatin1String(), QLatin1String("preCommentLocations") },
+ { QLatin1String(), QLatin1String("postCommentLocations") },
+ { QLatin1String(), QLatin1String("astComments") }
+ };
+ return FieldFilter { fieldFilterAdd, fieldFilterRemove };
+}
+
+void FieldFilter::setFiltred()
+{
+ auto types = domTypeToStringMap();
+ QSet<QString> filtredFieldStrs;
+ QSet<QString> filtredTypeStrs;
+ static QHash<QString, DomType> fieldToId = []() {
+ QHash<QString, DomType> res;
+ auto reverseMap = domTypeToStringMap();
+ auto it = reverseMap.cbegin();
+ auto end = reverseMap.cend();
+ while (it != end) {
+ res[it.value()] = it.key();
+ ++it;
+ }
+ return res;
+ }();
+ auto addFilteredOfMap = [&](const QMultiMap<QString, QString> &map) {
+ auto it = map.cbegin();
+ auto end = map.cend();
+ while (it != end) {
+ filtredTypeStrs.insert(it.key());
+ ++it;
+ }
+ for (auto f : map.values(QString()))
+ filtredFieldStrs.insert(f);
+ };
+ addFilteredOfMap(m_fieldFilterAdd);
+ addFilteredOfMap(m_fieldFilterRemove);
+ m_filtredDefault = true;
+ if (m_fieldFilterRemove.values(QString()).contains(QString()))
+ m_filtredDefault = false;
+ m_filtredFields.clear();
+ for (auto s : filtredFieldStrs)
+ if (!s.isEmpty())
+ m_filtredFields.insert(qHash(QStringView(s)));
+ m_filtredTypes.clear();
+ for (auto s : filtredTypeStrs) {
+ if (s.isEmpty())
+ continue;
+ if (fieldToId.contains(s)) {
+ m_filtredTypes.insert(fieldToId.value(s));
+ } else {
+ qCWarning(domLog) << "Filter on unknonw type " << s << " will be ignored";
+ }
+ }
+}
+
+} // end namespace Dom
+} // end namespace QQmlJS
+
+QT_END_NAMESPACE
diff --git a/src/qmldom/qqmldomfieldfilter_p.h b/src/qmldom/qqmldomfieldfilter_p.h
new file mode 100644
index 0000000000..6dad5da714
--- /dev/null
+++ b/src/qmldom/qqmldomfieldfilter_p.h
@@ -0,0 +1,100 @@
+/****************************************************************************
+**
+** Copyright (C) 2020 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the QtQml module of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** 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 The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/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 3 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL3 included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 3 requirements
+** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 2.0 or (at your option) the GNU General
+** Public license version 3 or any later version approved by the KDE Free
+** Qt Foundation. The licenses are as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-2.0.html and
+** https://www.gnu.org/licenses/gpl-3.0.html.
+**
+** $QT_END_LICENSE$
+**/
+#ifndef QQMLDOMFIELDFILTER_P_H
+#define QQMLDOMFIELDFILTER_P_H
+
+//
+// W A R N I N G
+// -------------
+//
+// This file is not part of the Qt API. It exists purely as an
+// implementation detail. This header file may change from version to
+// version without notice, or even be removed.
+//
+// We mean it.
+//
+
+#include "qqmldom_global.h"
+#include "qqmldomitem_p.h"
+#include "qqmldomastcreator_p.h"
+#include "qqmldomcomments_p.h"
+
+#include <QtQml/private/qqmljsastvisitor_p.h>
+
+QT_BEGIN_NAMESPACE
+
+namespace QQmlJS {
+namespace Dom {
+
+class QMLDOM_EXPORT FieldFilter
+{
+ Q_GADGET
+public:
+ QString describeFieldsFilter() const;
+ bool addFilter(QString f);
+ bool operator()(DomItem &, Path, DomItem &) const;
+ bool operator()(DomItem &, const PathEls::PathComponent &c, DomItem &) const;
+ static FieldFilter defaultFilter();
+ static FieldFilter noLocationFilter();
+ static FieldFilter compareFilter();
+ static FieldFilter compareNoCommentsFilter();
+ void setFiltred();
+ const QMultiMap<QString, QString> &fieldFilterAdd() const { return m_fieldFilterAdd; }
+ QMultiMap<QString, QString> fieldFilterRemove() const { return m_fieldFilterRemove; }
+ QSet<DomType> filtredTypes;
+
+ FieldFilter(const QMultiMap<QString, QString> &fieldFilterAdd = {},
+ const QMultiMap<QString, QString> &fieldFilterRemove = {})
+ : m_fieldFilterAdd(fieldFilterAdd), m_fieldFilterRemove(fieldFilterRemove)
+ {
+ setFiltred();
+ }
+
+private:
+ QMultiMap<QString, QString> m_fieldFilterAdd;
+ QMultiMap<QString, QString> m_fieldFilterRemove;
+ QSet<DomType> m_filtredTypes;
+ QSet<size_t> m_filtredFields;
+ bool m_filtredDefault = true;
+};
+
+} // end namespace Dom
+} // end namespace QQmlJS
+
+QT_END_NAMESPACE
+#endif // QQMLDOMFIELDFILTER_P_H
diff --git a/src/qmldom/qqmldomfilewriter.cpp b/src/qmldom/qqmldomfilewriter.cpp
new file mode 100644
index 0000000000..b24e5518ba
--- /dev/null
+++ b/src/qmldom/qqmldomfilewriter.cpp
@@ -0,0 +1,168 @@
+/****************************************************************************
+**
+** Copyright (C) 2021 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the QtQml module of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** 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 The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/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 3 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL3 included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 3 requirements
+** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 2.0 or (at your option) the GNU General
+** Public license version 3 or any later version approved by the KDE Free
+** Qt Foundation. The licenses are as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-2.0.html and
+** https://www.gnu.org/licenses/gpl-3.0.html.
+**
+** $QT_END_LICENSE$
+**/
+#include "qqmldomfilewriter_p.h"
+
+#include <QtCore/QRandomGenerator>
+#include <QtCore/QScopeGuard>
+
+QT_BEGIN_NAMESPACE
+namespace QQmlJS {
+namespace Dom {
+
+FileWriter::Status FileWriter::write(QString tFile, function_ref<bool(QTextStream &)> write,
+ int nBk)
+{
+ if (shouldRemoveTempFile)
+ tempFile.remove();
+ tempFile.close();
+ shouldRemoveTempFile = false;
+ Q_ASSERT(status != Status::ShouldWrite);
+ status = Status::ShouldWrite;
+ targetFile = tFile;
+ newBkFiles.clear();
+ warnings.clear();
+
+ int i = 0;
+ const int maxAttempts = 20;
+ for (; i < maxAttempts; ++i) {
+ tempFile.setFileName(targetFile
+ + QString::number(QRandomGenerator::global()->generate(), 16).mid(0, 8)
+ + QStringLiteral(u".tmp"));
+ if (tempFile.open(QIODevice::ReadWrite | QIODevice::NewOnly))
+ break;
+ }
+ if (i == maxAttempts) {
+ warnings.append(tr("Could not create temp file for %1").arg(targetFile));
+ status = FileWriter::Status::SkippedDueToFailure;
+ return status;
+ }
+ shouldRemoveTempFile = true;
+ bool success = false;
+ QTextStream inF(&tempFile);
+ QT_TRY
+ {
+ auto cleanup = qScopeGuard([this, &inF, &success, nBk] {
+ inF.flush();
+ tempFile.flush();
+ tempFile.close();
+ if (success) {
+ if (QFile::exists(targetFile)) {
+ // compareFiles
+ if (tempFile.open(QIODevice::ReadOnly)) {
+ auto closeTmpF = qScopeGuard([this] { tempFile.close(); });
+ QFile oldF(targetFile);
+ if (oldF.open(QIODevice::ReadOnly)) {
+ bool same = true;
+ while (!tempFile.atEnd() && !oldF.atEnd()) {
+ QByteArray l1 = tempFile.readLine();
+ QByteArray l2 = oldF.readLine();
+ if (l1 != l2)
+ same = false;
+ }
+ if (tempFile.atEnd() && oldF.atEnd() && same) {
+ tempFile.remove();
+ shouldRemoveTempFile = false;
+ status = Status::SkippedEqual;
+ return;
+ }
+ }
+ }
+ }
+ // move to target
+ int i = 0;
+ const int maxAttempts = 10;
+ for (; i < maxAttempts; ++i) {
+ if (QFile::exists(targetFile)) {
+ // make place for targetFile
+ QString bkFileName;
+ if (nBk < 1) {
+ QFile::remove(targetFile);
+ } else if (nBk == 1) {
+ QString bkFileName = targetFile + QStringLiteral(u"~");
+ QFile::remove(bkFileName);
+ QFile::rename(targetFile, bkFileName);
+ } else {
+ // f~ is the oldest, further backups at f1~ .. f<nBk>~
+ // keeping an empty place for the "next" backup
+ // f~ is never overwritten
+ int iBk = 0;
+ QString bkFileName = targetFile + QStringLiteral(u"~");
+ while (++iBk < nBk) {
+ if (QFile::exists(bkFileName))
+ bkFileName = targetFile + QString::number(iBk)
+ + QStringLiteral(u"~");
+ }
+ if (iBk == nBk)
+ QFile::remove(targetFile + QStringLiteral(u"1~"));
+ else
+ QFile::remove(targetFile + QString::number(++iBk)
+ + QStringLiteral(u"~"));
+ QFile::remove(bkFileName);
+ QFile::rename(targetFile, bkFileName);
+ }
+ if (!bkFileName.isEmpty() && QFile::rename(targetFile, bkFileName))
+ newBkFiles.append(bkFileName);
+ }
+ if (tempFile.rename(targetFile)) {
+ status = Status::DidWrite;
+ shouldRemoveTempFile = false;
+ return;
+ }
+ }
+ warnings.append(
+ tr("Rename of file %1 to %2 failed").arg(tempFile.fileName(), targetFile));
+ status = Status::SkippedDueToFailure;
+ } else {
+ warnings.append(tr("Error while writing"));
+ }
+ });
+ success = write(inF);
+ }
+ QT_CATCH(...)
+ {
+ warnings.append(tr("Exception trying to write file %1").arg(targetFile));
+ status = FileWriter::Status::SkippedDueToFailure;
+ }
+ if (status == Status::ShouldWrite)
+ status = Status::SkippedDueToFailure;
+ return status;
+}
+
+} // namespace Dom
+} // namespace QQmlJS
+QT_END_NAMESPACE
diff --git a/src/qmldom/qqmldomfilewriter_p.h b/src/qmldom/qqmldomfilewriter_p.h
new file mode 100644
index 0000000000..d7b90f9b32
--- /dev/null
+++ b/src/qmldom/qqmldomfilewriter_p.h
@@ -0,0 +1,98 @@
+/****************************************************************************
+**
+** Copyright (C) 2021 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the QtQml module of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** 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 The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/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 3 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL3 included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 3 requirements
+** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 2.0 or (at your option) the GNU General
+** Public license version 3 or any later version approved by the KDE Free
+** Qt Foundation. The licenses are as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-2.0.html and
+** https://www.gnu.org/licenses/gpl-3.0.html.
+**
+** $QT_END_LICENSE$
+**/
+#ifndef QQMLDOMFILEWRITER_P
+#define QQMLDOMFILEWRITER_P
+
+//
+// W A R N I N G
+// -------------
+//
+// This file is not part of the Qt API. It exists purely as an
+// implementation detail. This header file may change from version to
+// version without notice, or even be removed.
+//
+// We mean it.
+//
+
+#include "qqmldom_global.h"
+#include "qqmldomfunctionref_p.h"
+
+#include <QtCore/QFile>
+#include <QtCore/QStringList>
+#include <QtCore/QCoreApplication>
+
+QT_BEGIN_NAMESPACE
+namespace QQmlJS {
+namespace Dom {
+
+QMLDOM_EXPORT class FileWriter
+{
+ Q_GADGET
+ Q_DECLARE_TR_FUNCTIONS(FileWriter)
+public:
+ enum class Status { ShouldWrite, DidWrite, SkippedEqual, SkippedDueToFailure };
+
+ FileWriter() = default;
+
+ ~FileWriter()
+ {
+ if (!silentWarnings)
+ for (QString w : warnings)
+ qWarning() << w;
+ if (shouldRemoveTempFile)
+ tempFile.remove();
+ }
+
+ Status write(QString targetFile, function_ref<bool(QTextStream &)> write, int nBk = 2);
+
+ bool shouldRemoveTempFile = false;
+ bool silentWarnings = false;
+ Status status = Status::SkippedDueToFailure;
+ QString targetFile;
+ QFile tempFile;
+ QStringList newBkFiles;
+ QStringList warnings;
+
+private:
+ Q_DISABLE_COPY_MOVE(FileWriter)
+};
+
+} // namespace Dom
+} // namespace QQmlJS
+QT_END_NAMESPACE
+#endif
diff --git a/src/qmldom/qqmldomitem.cpp b/src/qmldom/qqmldomitem.cpp
index 272f88a66c..a2bffdb06d 100644
--- a/src/qmldom/qqmldomitem.cpp
+++ b/src/qmldom/qqmldomitem.cpp
@@ -41,6 +41,9 @@
#include "qqmldomexternalitems_p.h"
#include "qqmldommock_p.h"
#include "qqmldomastdumper_p.h"
+#include "qqmldomfilewriter_p.h"
+#include "qqmldomfieldfilter_p.h"
+#include "qqmldomcompare_p.h"
#include <QtQml/private/qqmljslexer_p.h>
#include <QtQml/private/qqmljsparser_p.h>
@@ -1923,6 +1926,32 @@ void DomItem::dump(Sink s, int indent,
visitEl([this, s, indent, filter](auto &&e) { e->dump(*this, s, indent, filter); });
}
+FileWriter::Status
+DomItem::dump(QString path,
+ function_ref<bool(DomItem &, const PathEls::PathComponent &, DomItem &)> filter,
+ int nBackups, int indent, FileWriter *fw)
+{
+ FileWriter localFw;
+ if (!fw)
+ fw = &localFw;
+ switch (fw->write(
+ path,
+ [this, indent, filter](QTextStream &ts) {
+ this->dump([&ts](QStringView s) { ts << s; }, indent, filter);
+ return true;
+ },
+ nBackups)) {
+ case FileWriter::Status::ShouldWrite:
+ case FileWriter::Status::SkippedDueToFailure:
+ qWarning() << "Failure dumping " << canonicalPath() << " to " << path;
+ break;
+ case FileWriter::Status::DidWrite:
+ case FileWriter::Status::SkippedEqual:
+ break;
+ }
+ return fw->status;
+}
+
QString DomItem::toString()
{
return dumperToString([this](Sink s){ dump(s); });
diff --git a/src/qmldom/qqmldomitem_p.h b/src/qmldom/qqmldomitem_p.h
index f94344e266..5dc9ac8c43 100644
--- a/src/qmldom/qqmldomitem_p.h
+++ b/src/qmldom/qqmldomitem_p.h
@@ -56,6 +56,7 @@
#include "qqmldompath_p.h"
#include "qqmldomerrormessage_p.h"
#include "qqmldomfunctionref_p.h"
+#include "qqmldomfilewriter_p.h"
#include <QtCore/QMap>
#include <QtCore/QMultiMap>
@@ -891,6 +892,10 @@ public:
void dump(Sink, int indent = 0,
function_ref<bool(DomItem &, const PathEls::PathComponent &, DomItem &)> filter =
noFilter);
+ FileWriter::Status
+ dump(QString path,
+ function_ref<bool(DomItem &, const PathEls::PathComponent &, DomItem &)> filter = noFilter,
+ int nBackups = 2, int indent = 0, FileWriter *fw = nullptr);
QString toString();
QString toString() const
{
@@ -1442,6 +1447,13 @@ public:
{
item().dump(s, indent, filter);
}
+ FileWriter::Status
+ dump(QString path,
+ function_ref<bool(DomItem &, const PathEls::PathComponent &, DomItem &)> filter = noFilter,
+ int nBackups = 2, int indent = 0, FileWriter *fw = nullptr)
+ {
+ return item().dump(path, filter, nBackups, indent, fw);
+ }
MutableDomItem fileLocations() { return MutableDomItem(item().fileLocations()); }
MutableDomItem makeCopy(CopyOption option = CopyOption::EnvConnected)
diff --git a/tests/auto/qmldom/CMakeLists.txt b/tests/auto/qmldom/CMakeLists.txt
index 7356648be3..ebdf993038 100644
--- a/tests/auto/qmldom/CMakeLists.txt
+++ b/tests/auto/qmldom/CMakeLists.txt
@@ -4,4 +4,5 @@ add_subdirectory(errormessage)
add_subdirectory(domitem)
add_subdirectory(path)
add_subdirectory(stringdumper)
+add_subdirectory(merging)
add_subdirectory(combined)
diff --git a/tests/auto/qmldom/combined/CMakeLists.txt b/tests/auto/qmldom/combined/CMakeLists.txt
index 2911fee2c9..ed2d492cdc 100644
--- a/tests/auto/qmldom/combined/CMakeLists.txt
+++ b/tests/auto/qmldom/combined/CMakeLists.txt
@@ -15,6 +15,7 @@ qt_internal_add_test(tst_dom_all
../errormessage/tst_qmldomerrormessage.cpp ../errormessage/tst_qmldomerrormessage.h
../path/tst_qmldompath.h
../domitem/tst_qmldomitem.h
+ ../merging/tst_dommerging.h
INCLUDE_DIRECTORIES
..
DEFINES
diff --git a/tests/auto/qmldom/combined/tst_dom_all.cpp b/tests/auto/qmldom/combined/tst_dom_all.cpp
index 182ac1db46..91ec70db83 100644
--- a/tests/auto/qmldom/combined/tst_dom_all.cpp
+++ b/tests/auto/qmldom/combined/tst_dom_all.cpp
@@ -38,12 +38,12 @@
#include "stringdumper/tst_qmldomstringdumper.h"
#include "errormessage/tst_qmldomerrormessage.h"
#include "domitem/tst_qmldomitem.h"
+#include "merging/tst_dommerging.h"
#include "path/tst_qmldompath.h"
int main(int argc, char *argv[])
{
int status = 0;
-
{
QQmlJS::Dom::TestStringDumper test;
status |= QTest::qExec(&test, argc, argv);
@@ -60,5 +60,9 @@ int main(int argc, char *argv[])
QQmlJS::Dom::TestDomItem test;
status |= QTest::qExec(&test, argc, argv);
}
+ {
+ QQmlJS::Dom::TestDomMerging test;
+ status |= QTest::qExec(&test, argc, argv);
+ }
return status;
}
diff --git a/tests/auto/qmldom/domdata/dommerging/test1.qml b/tests/auto/qmldom/domdata/dommerging/test1.qml
new file mode 100644
index 0000000000..6347510c3a
--- /dev/null
+++ b/tests/auto/qmldom/domdata/dommerging/test1.qml
@@ -0,0 +1,12 @@
+import QtQuick
+
+Rectangle {
+id: root
+
+width: { height *3/4 }
+height: 480
+Rectangle {
+ width: 58
+ heigth:80
+}
+}
diff --git a/tests/auto/qmldom/merging/CMakeLists.txt b/tests/auto/qmldom/merging/CMakeLists.txt
new file mode 100644
index 0000000000..5815d1b319
--- /dev/null
+++ b/tests/auto/qmldom/merging/CMakeLists.txt
@@ -0,0 +1,29 @@
+# Generated from domitem.pro.
+
+#####################################################################
+## tst_qmldomitem Binary:
+#####################################################################
+# Collect test data
+file(GLOB_RECURSE test_data_glob
+ RELATIVE ${CMAKE_CURRENT_SOURCE_DIR}/..
+ domdata/dommerging)
+list(APPEND test_data ${test_data_glob})
+
+qt_internal_add_test(tst_dommerging
+ SOURCES
+ tst_dommerging.cpp tst_dommerging.h
+ DEFINES
+ QT_DEPRECATED_WARNINGS
+ QT_QMLTEST_DATADIR=\\\"${CMAKE_CURRENT_SOURCE_DIR}/../domdata\\\"
+ PUBLIC_LIBRARIES
+ Qt::Core
+ Qt::QmlDevToolsPrivate
+ Qt::QmlDomPrivate
+ Qt::Test
+ TESTDATA ${test_data}
+)
+
+qt_internal_extend_target(tst_qmldomitem CONDITION ANDROID OR IOS
+ DEFINES
+ QT_QMLTEST_DATADIR=\\\":/domdata\\\"
+)
diff --git a/tests/auto/qmldom/merging/tst_dommerging.cpp b/tests/auto/qmldom/merging/tst_dommerging.cpp
new file mode 100644
index 0000000000..357497a2b8
--- /dev/null
+++ b/tests/auto/qmldom/merging/tst_dommerging.cpp
@@ -0,0 +1,40 @@
+/****************************************************************************
+**
+** Copyright (C) 2020 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the QtQml module of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** 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 The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/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 3 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL3 included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 3 requirements
+** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 2.0 or (at your option) the GNU General
+** Public license version 3 or any later version approved by the KDE Free
+** Qt Foundation. The licenses are as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-2.0.html and
+** https://www.gnu.org/licenses/gpl-3.0.html.
+**
+** $QT_END_LICENSE$
+**/
+#include "tst_dommerging.h"
+
+QTEST_MAIN(QQmlJS::Dom::TestDomMerging)
diff --git a/tests/auto/qmldom/merging/tst_dommerging.h b/tests/auto/qmldom/merging/tst_dommerging.h
new file mode 100644
index 0000000000..e7e48fbb09
--- /dev/null
+++ b/tests/auto/qmldom/merging/tst_dommerging.h
@@ -0,0 +1,143 @@
+/****************************************************************************
+**
+** Copyright (C) 2021 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the QtQml module of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** 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 The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/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 3 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL3 included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 3 requirements
+** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 2.0 or (at your option) the GNU General
+** Public license version 3 or any later version approved by the KDE Free
+** Qt Foundation. The licenses are as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-2.0.html and
+** https://www.gnu.org/licenses/gpl-3.0.html.
+**
+** $QT_END_LICENSE$
+**/
+#ifndef TST_DOMMERGING_H
+#define TST_DOMMERGING_H
+#include <QtQmlDom/private/qqmldomitem_p.h>
+#include <QtQmlDom/private/qqmldomtop_p.h>
+#include <QtQmlDom/private/qqmldomastdumper_p.h>
+
+#include <QtTest/QtTest>
+#include <QCborValue>
+#include <QDebug>
+#include <QLibraryInfo>
+
+#include <memory>
+
+QT_BEGIN_NAMESPACE
+namespace QQmlJS {
+namespace Dom {
+
+class QMLDOM_EXPORT TestDomMerging : public QObject
+{
+ Q_OBJECT
+public:
+ static ErrorGroups myErrors()
+ {
+ static ErrorGroups res { { NewErrorGroup("tests"), NewErrorGroup("domitem") } };
+ return res;
+ }
+
+private slots:
+ void initTestCase()
+ {
+ QString baseDir = QLatin1String(QT_QMLTEST_DATADIR) + QLatin1String("/dommerging");
+ QStringList qmltypeDirs =
+ QStringList({ baseDir, QLibraryInfo::path(QLibraryInfo::Qml2ImportsPath) });
+
+ auto envPtr = std::shared_ptr<QQmlJS::Dom::DomEnvironment>(new QQmlJS::Dom::DomEnvironment(
+ qmltypeDirs,
+ DomEnvironment::Option::SingleThreaded | DomEnvironment::Option::NoDependencies));
+ QQmlJS::Dom::DomItem env(envPtr);
+ QVERIFY(env);
+ QString testFile1 = baseDir + QLatin1String("/test1.qml");
+
+ env.loadFile(
+ testFile1, QString(),
+ [this](Path, const DomItem &, const DomItem &newIt) { this->tFile = newIt; },
+ LoadOption::DefaultLoad);
+ env.loadFile(baseDir, QString(), {}, LoadOption::DefaultLoad);
+ envPtr->loadPendingDependencies(env);
+
+ QVERIFY(tFile);
+ tFile = tFile.field(Fields::currentItem);
+ QVERIFY(tFile);
+ }
+
+ void testReformat()
+ {
+ DomItem comp1 = tFile.field(Fields::components).key(QString()).index(0);
+ QVERIFY(comp1);
+ DomItem obj1 = comp1.field(Fields::objects).index(0);
+ QVERIFY(obj1);
+ DomItem width = obj1.field(Fields::bindings).key(QLatin1String("width")).index(0);
+ DomItem w = obj1.bindings().key(QLatin1String("width"));
+ QVERIFY(w.length() > 0);
+ QCOMPARE(w.length(), 1);
+ DomItem exp = width.field(Fields::value);
+ if (std::shared_ptr<ScriptExpression> expPtr = exp.ownerAs<ScriptExpression>()) {
+ QCOMPARE(expPtr->code(), u"{ height *3/4 }");
+ }
+ MutableDomItem myobj(obj1);
+ PropertyDefinition pDef;
+ pDef.name = QLatin1String("foo");
+ pDef.typeName = QLatin1String("int");
+ MutableDomItem propertyDef = myobj.addPropertyDef(pDef, AddOption::Overwrite);
+ QVERIFY(propertyDef);
+ MutableDomItem binding = myobj.addBinding(
+ Binding(QLatin1String("foo"),
+ std::shared_ptr<ScriptExpression>(new ScriptExpression(
+ QLatin1String("42"),
+ ScriptExpression::ExpressionType::BindingExpression))),
+ AddOption::Overwrite);
+ QVERIFY(binding);
+ MutableDomItem pInfo = myobj.field(Fields::propertyInfos).key(QLatin1String("foo"));
+ // dumperToQDebug([pInfo](Sink s){ pInfo.dump(s); });
+ QCOMPARE(propertyDef.item(), pInfo.field(Fields::propertyDefs).index(0).item());
+ QCOMPARE(binding, pInfo.field(Fields::bindings).index(0));
+ }
+
+ void testFromScratch()
+ {
+ // QmlFile
+ // add import
+ // set prop
+ // add prop
+ // ubObj
+ }
+
+private:
+ std::shared_ptr<DomEnvironment> envPtr;
+ DomItem env;
+ DomItem tFile;
+};
+
+} // namespace Dom
+} // namespace QQmlJS
+QT_END_NAMESPACE
+
+#endif
diff --git a/tools/CMakeLists.txt b/tools/CMakeLists.txt
index aef0788de2..01fa0bf043 100644
--- a/tools/CMakeLists.txt
+++ b/tools/CMakeLists.txt
@@ -8,6 +8,7 @@ qt_exclude_tool_directories_from_default_target(
)
if(QT_FEATURE_qml_devtools)
+ add_subdirectory(qmldom)
add_subdirectory(qmllint)
add_subdirectory(qmlimportscanner)
add_subdirectory(qmlformat)
diff --git a/tools/qmldom/CMakeLists.txt b/tools/qmldom/CMakeLists.txt
new file mode 100644
index 0000000000..b0a01d2a7b
--- /dev/null
+++ b/tools/qmldom/CMakeLists.txt
@@ -0,0 +1,15 @@
+#####################################################################
+## qmldom Tool:
+#####################################################################
+
+qt_get_tool_target_name(target_name qmldom)
+qt_internal_add_tool(${target_name}
+ TARGET_DESCRIPTION "QML Dom handler"
+ TOOLS_TARGET Qml # special case
+ SOURCES
+ qmldomtool.cpp
+ PUBLIC_LIBRARIES
+ Qt::CorePrivate
+ Qt::QmlDevToolsPrivate
+ Qt::QmlDomPrivate
+)
diff --git a/tools/qmldom/qmldomtool.cpp b/tools/qmldom/qmldomtool.cpp
new file mode 100644
index 0000000000..760d43222e
--- /dev/null
+++ b/tools/qmldom/qmldomtool.cpp
@@ -0,0 +1,228 @@
+/****************************************************************************
+**
+** Copyright (C) 2020 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the tools applications of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:GPL-EXCEPT$
+** 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 The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3 as published by the Free Software
+** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-3.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include <QtCore/qdebug.h>
+#include <QtCore/qfile.h>
+#include <QtCore/qfileinfo.h>
+#include <QtCore/qcoreapplication.h>
+#include <QtCore/qdir.h>
+#include <QtCore/QTextStream>
+#include <QtCore/QThread>
+
+#include <QtQmlDom/private/qqmldomtop_p.h>
+#include <QtQmlDom/private/qqmldomfilewriter_p.h>
+#include <QtQmlDom/private/qqmldomelements_p.h>
+#include <QtQmlDom/private/qqmldomfieldfilter_p.h>
+
+#include <cstdio>
+
+#if QT_CONFIG(commandlineparser)
+# include <QtCore/qcommandlineparser.h>
+#endif
+
+#ifndef QT_BOOTSTRAPPED
+# include <QtCore/qlibraryinfo.h>
+#endif
+using namespace QQmlJS::Dom;
+
+namespace tt {
+Q_NAMESPACE
+
+enum class Dependencies { None, Required };
+Q_ENUM_NS(Dependencies);
+
+};
+using namespace tt;
+
+int main(int argc, char *argv[])
+{
+ FieldFilter filter = FieldFilter::defaultFilter();
+ QCoreApplication a(argc, argv);
+ QCoreApplication::setApplicationName("qmldom");
+ QCoreApplication::setApplicationVersion("1.0");
+#if QT_CONFIG(commandlineparser)
+ QCommandLineParser parser;
+ parser.setApplicationDescription(QLatin1String("QML dom tool"));
+ parser.addHelpOption();
+ parser.addVersionOption();
+
+ QCommandLineOption dumpOption(QStringList() << "d"
+ << "dump",
+ QLatin1String("Dumps the code model"));
+ parser.addOption(dumpOption);
+
+ QCommandLineOption filterOption(
+ QStringList() << "f"
+ << "filter-fields",
+ QLatin1String("commas separated list of fields to filter out. Prepending a field with "
+ "'-' skips the field, with '+' it adds it. The field might be prepended "
+ "by '<type>:' to apply only to elements of that type"
+ "The default filters are ")
+ + filter.describeFieldsFilter(),
+ QLatin1String("fields"));
+ parser.addOption(filterOption);
+
+ QCommandLineOption qmltypesDirsOption(
+ QStringList() << "I"
+ << "qmldirs",
+ QLatin1String("Look for qmltypes files in specified directory"),
+ QLatin1String("directory"));
+ parser.addOption(qmltypesDirsOption);
+
+ QCommandLineOption qmltypesFilesOption(QStringList() << "i"
+ << "qmltypes",
+ QLatin1String("Include the specified qmltypes files"),
+ QLatin1String("qmltypes"));
+ parser.addOption(qmltypesFilesOption);
+
+ QCommandLineOption pathToDumpOption(
+ QStringList() << "path-to-dump",
+ QLatin1String("adds a path to dump (by default the root path is dumped)"),
+ QLatin1String("pathToDump"));
+ parser.addOption(pathToDumpOption);
+
+ QCommandLineOption nBackupsOption(
+ QStringList() << "backups",
+ QLatin1String("Number of backup files to generate (default is 2, the oldest, "
+ "and the last version are kept), "),
+ QLatin1String("nBackups"));
+ parser.addOption(nBackupsOption);
+
+ parser.addPositionalArgument(QLatin1String("files"),
+ QLatin1String("list of qml or js files to verify"));
+
+ parser.process(a);
+
+ const auto positionalArguments = parser.positionalArguments();
+ if (positionalArguments.isEmpty()) {
+ parser.showHelp(-1);
+ }
+
+ if (parser.isSet(filterOption)) {
+ qDebug() << "filters: " << parser.values(filterOption);
+ for (QString fFields : parser.values(filterOption)) {
+ if (!filter.addFilter(fFields)) {
+ return 1;
+ }
+ }
+ }
+
+ Dependencies dep = Dependencies::None;
+
+ int nBackups = 2;
+ if (parser.isSet(nBackupsOption)) {
+ bool intOk;
+ nBackups = parser.value(nBackupsOption).toInt(&intOk);
+ if (!intOk) {
+ qDebug() << "expected an integer giving the number of backups after --backups, not "
+ << parser.value(nBackupsOption);
+ }
+ }
+
+ QList<Path> pathsToDump;
+ for (QString pStr : parser.values(pathToDumpOption)) {
+ pathsToDump.append(Path::fromString(pStr));
+ }
+ if (pathsToDump.isEmpty())
+ pathsToDump.append(Path());
+
+ // use host qml import path as a sane default if nothing else has been provided
+ QStringList qmltypeDirs = parser.isSet(qmltypesDirsOption)
+ ? parser.values(qmltypesDirsOption)
+# ifndef QT_BOOTSTRAPPED
+ : QStringList { QLibraryInfo::path(QLibraryInfo::Qml2ImportsPath) };
+# else
+ : QStringList {};
+# endif
+
+ if (!parser.isSet(qmltypesFilesOption))
+ qmltypeDirs << ".";
+
+ QStringList qmltypeFiles =
+ parser.isSet(qmltypesFilesOption) ? parser.values(qmltypesFilesOption) : QStringList {};
+#else
+ QStringList qmltypeDirs {};
+ QStringList qmltypeFiles {};
+#endif
+
+ {
+ QDebug dbg = qDebug();
+ dbg << "dirs:\n";
+ foreach (QString d, qmltypeDirs)
+ dbg << " '" << d << "'\n";
+ dbg << "files:\n";
+ foreach (QString f, positionalArguments)
+ dbg << " '" << f << "'\n";
+ dbg << "fieldFilter: " << filter.describeFieldsFilter();
+ dbg << "\n";
+ }
+ QQmlJS::Dom::DomEnvironment::Options options =
+ QQmlJS::Dom::DomEnvironment::Option::SingleThreaded;
+ if (dep == Dependencies::None)
+ options = options | QQmlJS::Dom::DomEnvironment::Option::NoDependencies;
+ std::shared_ptr<QQmlJS::Dom::DomEnvironment> envPtr(
+ new QQmlJS::Dom::DomEnvironment(qmltypeDirs, options));
+ QQmlJS::Dom::DomItem env(envPtr);
+ qDebug() << "will load\n";
+ if (dep != Dependencies::None)
+ env.loadBuiltins();
+ foreach (QString s, positionalArguments) {
+ env.loadFile(s, QString(), nullptr, LoadOption::DefaultLoad);
+ }
+ envPtr->loadPendingDependencies(env);
+ {
+ qDebug() << "will dump\n";
+ QTextStream ts(stdout);
+ auto sink = [&ts](QStringView v) {
+ ts << v; /* ts.flush(); */
+ };
+ if (pathsToDump.length() > 1)
+ sink(u"{\n");
+ bool first = true;
+ for (Path p : pathsToDump) {
+ if (pathsToDump.length() > 1) {
+ if (first)
+ first = false;
+ else
+ sink(u",\n");
+ sinkEscaped(sink, p.toString());
+ sink(u":\n");
+ }
+ env.path(p).dump(sink, 0, filter);
+ }
+ if (pathsToDump.length() > 1)
+ sink(u"}\n");
+ Qt::endl(ts).flush();
+ }
+ for (int i = 0; i < 100; ++i)
+ QThread::yieldCurrentThread(); // let buggy integrations catch up with the output
+ // return a.exec();
+ return 0;
+}
+
+#include "qmldomtool.moc"