aboutsummaryrefslogtreecommitdiffstats
path: root/src
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 /src
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>
Diffstat (limited to 'src')
-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
9 files changed, 1014 insertions, 0 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)