From 6cf15ad4e359223bb19c997f0dd279f9aea842d7 Mon Sep 17 00:00:00 2001 From: Fawzi Mohamed Date: Tue, 23 Mar 2021 12:04:19 +0100 Subject: 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 --- src/qmldom/CMakeLists.txt | 3 + src/qmldom/qqmldomcompare.cpp | 259 ++++++++++++++++++++++++++++++++++++++ src/qmldom/qqmldomcompare_p.h | 106 ++++++++++++++++ src/qmldom/qqmldomfieldfilter.cpp | 239 +++++++++++++++++++++++++++++++++++ src/qmldom/qqmldomfieldfilter_p.h | 100 +++++++++++++++ src/qmldom/qqmldomfilewriter.cpp | 168 +++++++++++++++++++++++++ src/qmldom/qqmldomfilewriter_p.h | 98 +++++++++++++++ src/qmldom/qqmldomitem.cpp | 29 +++++ src/qmldom/qqmldomitem_p.h | 12 ++ 9 files changed, 1014 insertions(+) create mode 100644 src/qmldom/qqmldomcompare.cpp create mode 100644 src/qmldom/qqmldomcompare_p.h create mode 100644 src/qmldom/qqmldomfieldfilter.cpp create mode 100644 src/qmldom/qqmldomfieldfilter_p.h create mode 100644 src/qmldom/qqmldomfilewriter.cpp create mode 100644 src/qmldom/qqmldomfilewriter_p.h (limited to 'src') 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 change, + function_ref 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 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 + +QT_BEGIN_NAMESPACE + +namespace QQmlJS { +namespace Dom { + +bool domCompare( + DomItem &i1, DomItem &i2, function_ref change, + function_ref filter = noFilter, + Path p = Path()); + +enum DomCompareStrList { FirstDiff, AllDiffs }; + +QStringList domCompareStrList( + DomItem &i1, DomItem &i2, + function_ref filter = noFilter, + DomCompareStrList stopAtFirstDiff = DomCompareStrList::FirstDiff); + +inline QStringList domCompareStrList( + MutableDomItem &i1, DomItem &i2, + function_ref 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 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 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 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 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"((?[-+])?(?:(?[a-zA-Z0-9_]*):)?(?[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 fieldFilterAdd { { QLatin1String("ScriptExpression"), + QLatin1String("code") } }; + QMultiMap fieldFilterRemove { { QString(), QLatin1String("code") }, + { QString(), QLatin1String("propertyInfos") }, + { QLatin1String("AttachedInfo"), + QLatin1String("parent") } }; + + return FieldFilter { fieldFilterAdd, fieldFilterRemove }; +} + +QQmlJS::Dom::FieldFilter QQmlJS::Dom::FieldFilter::noLocationFilter() +{ + QMultiMap fieldFilterAdd {}; + QMultiMap 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 fieldFilterAdd {}; + QMultiMap 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 fieldFilterAdd {}; + QMultiMap 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 filtredFieldStrs; + QSet filtredTypeStrs; + static QHash fieldToId = []() { + QHash 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 &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 + +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 &fieldFilterAdd() const { return m_fieldFilterAdd; } + QMultiMap fieldFilterRemove() const { return m_fieldFilterRemove; } + QSet filtredTypes; + + FieldFilter(const QMultiMap &fieldFilterAdd = {}, + const QMultiMap &fieldFilterRemove = {}) + : m_fieldFilterAdd(fieldFilterAdd), m_fieldFilterRemove(fieldFilterRemove) + { + setFiltred(); + } + +private: + QMultiMap m_fieldFilterAdd; + QMultiMap m_fieldFilterRemove; + QSet m_filtredTypes; + QSet 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 +#include + +QT_BEGIN_NAMESPACE +namespace QQmlJS { +namespace Dom { + +FileWriter::Status FileWriter::write(QString tFile, function_ref 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~ + // 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 +#include +#include + +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 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 #include @@ -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 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 #include @@ -891,6 +892,10 @@ public: void dump(Sink, int indent = 0, function_ref filter = noFilter); + FileWriter::Status + dump(QString path, + function_ref 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 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) -- cgit v1.2.3