diff options
author | Fawzi Mohamed <fawzi.mohamed@qt.io> | 2021-03-23 12:04:19 +0100 |
---|---|---|
committer | Fawzi Mohamed <fawzi.mohamed@qt.io> | 2021-06-05 00:07:38 +0200 |
commit | 6cf15ad4e359223bb19c997f0dd279f9aea842d7 (patch) | |
tree | d29f61d703771d5f16795e3535497aa71068f071 | |
parent | 52d61e705ee606f3b673c757bdf253bdf6134a3b (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.txt | 3 | ||||
-rw-r--r-- | src/qmldom/qqmldomcompare.cpp | 259 | ||||
-rw-r--r-- | src/qmldom/qqmldomcompare_p.h | 106 | ||||
-rw-r--r-- | src/qmldom/qqmldomfieldfilter.cpp | 239 | ||||
-rw-r--r-- | src/qmldom/qqmldomfieldfilter_p.h | 100 | ||||
-rw-r--r-- | src/qmldom/qqmldomfilewriter.cpp | 168 | ||||
-rw-r--r-- | src/qmldom/qqmldomfilewriter_p.h | 98 | ||||
-rw-r--r-- | src/qmldom/qqmldomitem.cpp | 29 | ||||
-rw-r--r-- | src/qmldom/qqmldomitem_p.h | 12 | ||||
-rw-r--r-- | tests/auto/qmldom/CMakeLists.txt | 1 | ||||
-rw-r--r-- | tests/auto/qmldom/combined/CMakeLists.txt | 1 | ||||
-rw-r--r-- | tests/auto/qmldom/combined/tst_dom_all.cpp | 6 | ||||
-rw-r--r-- | tests/auto/qmldom/domdata/dommerging/test1.qml | 12 | ||||
-rw-r--r-- | tests/auto/qmldom/merging/CMakeLists.txt | 29 | ||||
-rw-r--r-- | tests/auto/qmldom/merging/tst_dommerging.cpp | 40 | ||||
-rw-r--r-- | tests/auto/qmldom/merging/tst_dommerging.h | 143 | ||||
-rw-r--r-- | tools/CMakeLists.txt | 1 | ||||
-rw-r--r-- | tools/qmldom/CMakeLists.txt | 15 | ||||
-rw-r--r-- | tools/qmldom/qmldomtool.cpp | 228 |
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" |