aboutsummaryrefslogtreecommitdiffstats
path: root/src/qmldom
diff options
context:
space:
mode:
Diffstat (limited to 'src/qmldom')
-rw-r--r--src/qmldom/.prev_CMakeLists.txt29
-rw-r--r--src/qmldom/CMakeLists.txt35
-rw-r--r--src/qmldom/qqmldom_fwd_p.h134
-rw-r--r--src/qmldom/qqmldom_global.h40
-rw-r--r--src/qmldom/qqmldom_utils.cpp63
-rw-r--r--src/qmldom/qqmldom_utils_p.h53
-rw-r--r--src/qmldom/qqmldomastcreator.cpp3031
-rw-r--r--src/qmldom/qqmldomastcreator_p.h710
-rw-r--r--src/qmldom/qqmldomastdumper.cpp1106
-rw-r--r--src/qmldom/qqmldomastdumper_p.h56
-rw-r--r--src/qmldom/qqmldomattachedinfo.cpp345
-rw-r--r--src/qmldom/qqmldomattachedinfo_p.h311
-rw-r--r--src/qmldom/qqmldomcodeformatter.cpp1409
-rw-r--r--src/qmldom/qqmldomcodeformatter_p.h275
-rw-r--r--src/qmldom/qqmldomcomments.cpp766
-rw-r--r--src/qmldom/qqmldomcomments_p.h360
-rw-r--r--src/qmldom/qqmldomcompare.cpp232
-rw-r--r--src/qmldom/qqmldomcompare_p.h72
-rw-r--r--src/qmldom/qqmldomconstants.cpp13
-rw-r--r--src/qmldom/qqmldomconstants_p.h374
-rw-r--r--src/qmldom/qqmldomelements.cpp2196
-rw-r--r--src/qmldom/qqmldomelements_p.h1272
-rw-r--r--src/qmldom/qqmldomerrormessage.cpp194
-rw-r--r--src/qmldom/qqmldomerrormessage_p.h174
-rw-r--r--src/qmldom/qqmldomexternalitems.cpp629
-rw-r--r--src/qmldom/qqmldomexternalitems_p.h575
-rw-r--r--src/qmldom/qqmldomfieldfilter.cpp258
-rw-r--r--src/qmldom/qqmldomfieldfilter_p.h69
-rw-r--r--src/qmldom/qqmldomfilewriter.cpp135
-rw-r--r--src/qmldom/qqmldomfilewriter_p.h65
-rw-r--r--src/qmldom/qqmldomfunctionref_p.h60
-rw-r--r--src/qmldom/qqmldomindentinglinewriter.cpp126
-rw-r--r--src/qmldom/qqmldomindentinglinewriter_p.h62
-rw-r--r--src/qmldom/qqmldomitem.cpp3444
-rw-r--r--src/qmldom/qqmldomitem_p.h2579
-rw-r--r--src/qmldom/qqmldomlinewriter.cpp456
-rw-r--r--src/qmldom/qqmldomlinewriter_p.h225
-rw-r--r--src/qmldom/qqmldommock.cpp151
-rw-r--r--src/qmldom/qqmldommock_p.h113
-rw-r--r--src/qmldom/qqmldommoduleindex.cpp407
-rw-r--r--src/qmldom/qqmldommoduleindex_p.h141
-rw-r--r--src/qmldom/qqmldomoutwriter.cpp368
-rw-r--r--src/qmldom/qqmldomoutwriter_p.h164
-rw-r--r--src/qmldom/qqmldompath.cpp485
-rw-r--r--src/qmldom/qqmldompath_p.h880
-rw-r--r--src/qmldom/qqmldomreformatter.cpp1136
-rw-r--r--src/qmldom/qqmldomreformatter_p.h223
-rw-r--r--src/qmldom/qqmldomscanner.cpp448
-rw-r--r--src/qmldom/qqmldomscanner_p.h98
-rw-r--r--src/qmldom/qqmldomscriptelements.cpp355
-rw-r--r--src/qmldom/qqmldomscriptelements_p.h405
-rw-r--r--src/qmldom/qqmldomstringdumper.cpp83
-rw-r--r--src/qmldom/qqmldomstringdumper_p.h74
-rw-r--r--src/qmldom/qqmldomtop.cpp2341
-rw-r--r--src/qmldom/qqmldomtop_p.h1038
-rw-r--r--src/qmldom/qqmldomtypesreader.cpp272
-rw-r--r--src/qmldom/qqmldomtypesreader_p.h68
57 files changed, 28015 insertions, 3168 deletions
diff --git a/src/qmldom/.prev_CMakeLists.txt b/src/qmldom/.prev_CMakeLists.txt
deleted file mode 100644
index 39a82e3ec0..0000000000
--- a/src/qmldom/.prev_CMakeLists.txt
+++ /dev/null
@@ -1,29 +0,0 @@
-# Generated from qmldom.pro.
-
-#####################################################################
-## QmlDom Module:
-#####################################################################
-
-qt_internal_add_module(QmlDom
- STATIC
- INTERNAL_MODULE
- NO_SYNC_QT
- SOURCES
- qqmldom_fwd_p.h
- qqmldom_global.h
- qqmldomconstants_p.h
- qqmldomerrormessage.cpp qqmldomerrormessage_p.h
- qqmldomexternalitems.cpp qqmldomexternalitems_p.h
- qqmldomitem.cpp qqmldomitem_p.h
- qqmldompath.cpp qqmldompath_p.h
- qqmldomstringdumper.cpp qqmldomstringdumper_p.h
- qqmldomtop.cpp qqmldomtop_p.h
- DEFINES
- QMLDOM_LIBRARY
- PUBLIC_LIBRARIES
- Qt::CorePrivate
- Qt::QmlDevToolsPrivate
-)
-
-#### Keys ignored in scope 1:.:.:qmldom.pro:<TRUE>:
-# _OPTION = "host_build"
diff --git a/src/qmldom/CMakeLists.txt b/src/qmldom/CMakeLists.txt
index 491df40ef5..f0fffea605 100644
--- a/src/qmldom/CMakeLists.txt
+++ b/src/qmldom/CMakeLists.txt
@@ -1,28 +1,53 @@
+# Copyright (C) 2022 The Qt Company Ltd.
+# SPDX-License-Identifier: BSD-3-Clause
+
# Generated from qmldom.pro.
#####################################################################
-## QmlDom Module:
+## QmlDomPrivate Module:
#####################################################################
-qt_internal_add_module(QmlDom
+qt_internal_add_module(QmlDomPrivate
STATIC
INTERNAL_MODULE
# NO_SYNC_QT # special case remove, it's actually needed for tests
SOURCES
qqmldom_fwd_p.h
qqmldom_global.h
- qqmldomconstants_p.h
+ qqmldom_utils_p.h qqmldom_utils.cpp
+ qqmldomastcreator.cpp qqmldomastcreator_p.h
+ qqmldomastdumper.cpp qqmldomastdumper_p.h
+ qqmldomattachedinfo.cpp qqmldomattachedinfo_p.h
+ qqmldomcodeformatter.cpp qqmldomcodeformatter_p.h
+ qqmldomcomments.cpp qqmldomcomments_p.h
+ qqmldomcompare.cpp qqmldomcompare_p.h
+ qqmldomconstants.cpp 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
+ qqmldomindentinglinewriter.cpp qqmldomindentinglinewriter_p.h
qqmldomitem.cpp qqmldomitem_p.h
+ qqmldommock.cpp qqmldommock_p.h
+ qqmldomlinewriter.cpp qqmldomlinewriter_p.h
+ qqmldommoduleindex.cpp qqmldommoduleindex_p.h
+ qqmldomoutwriter.cpp qqmldomoutwriter_p.h
qqmldompath.cpp qqmldompath_p.h
qqmldomstringdumper.cpp qqmldomstringdumper_p.h
+ qqmldomreformatter.cpp qqmldomreformatter_p.h
+ qqmldomscanner.cpp qqmldomscanner_p.h
qqmldomtop.cpp qqmldomtop_p.h
+ qqmldomtypesreader.cpp qqmldomtypesreader_p.h
+ qqmldomscriptelements_p.h qqmldomscriptelements.cpp
DEFINES
QMLDOM_LIBRARY
PUBLIC_LIBRARIES
- Qt::CorePrivate
- Qt::QmlDevToolsPrivate
+ Qt::QmlPrivate
+ Qt::QmlCompilerPrivate
+ NO_UNITY_BUILD
+ NO_GENERATE_CPP_EXPORTS
)
#### Keys ignored in scope 1:.:.:qmldom.pro:<TRUE>:
diff --git a/src/qmldom/qqmldom_fwd_p.h b/src/qmldom/qqmldom_fwd_p.h
index a8b54b1e51..9b8603b33e 100644
--- a/src/qmldom/qqmldom_fwd_p.h
+++ b/src/qmldom/qqmldom_fwd_p.h
@@ -1,40 +1,6 @@
-/****************************************************************************
-**
-** 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$
-**/
+// Copyright (C) 2020 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+
#ifndef QQMLDOM_FWD_P_H
#define QQMLDOM_FWD_P_H
@@ -50,53 +16,87 @@
//
#include "qqmldom_global.h"
+#include "private/qglobal_p.h"
QT_BEGIN_NAMESPACE
namespace QQmlJS {
namespace Dom {
-class ExternalItemInfoBase;
-class ExternalItemPairBase;
-class ExternalOwningItem;
-class OwningItem;
+class AstComments;
+class AttachedInfo;
+class Binding;
+class Comment;
+class CommentedElement;
+class ConstantData;
class DomBase;
+enum DomCreationOption : char;
+class DomEnvironment;
class DomItem;
-class Source;
+class DomTop;
+class DomUniverse;
class Empty;
-class QmlDirectory;
+class EnumDecl;
+class Export;
+class ExternalItemInfoBase;
+class ExternalItemPairBase;
+class ExternalOwningItem;
+class FileLocations;
+enum FileLocationRegion : int;
+class FileWriter;
+class GlobalComponent;
+class GlobalScope;
+class MockObject;
+class MockOwner;
+class Id;
+class Import;
class JsFile;
-class QmlFile;
-class QmltypesFile;
+class JsResource;
+class List;
+class LoadInfo;
+class Map;
+class MethodInfo;
class ModuleIndex;
class ModuleScope;
-class Export;
-class JsResource;
-class QmltypesComponent;
-class QmlComponent;
-class EnumDecl;
-class Import;
+class MutableDomItem;
+class ObserversTrie;
+class OutWriter;
+class OutWriterState;
+class OwningItem;
+class Path;
class Pragma;
-class Id;
+class PropertyDefinition;
+class PropertyInfo;
+class QQmlDomAstCreator;
+class QmlComponent;
+class QmlDirectory;
+class QmldirFile;
+class QmlFile;
class QmlObject;
-class ConstantData;
-class ScriptExpression;
+class QmltypesComponent;
+class QmltypesFile;
class Reference;
-class Binding;
-class PropertyDefinition;
-class RequiredProperty;
-class Version;
-class MethodInfo;
-class GenericObject;
-class Map;
-class List;
+class RegionComments;
+class ScriptExpression;
+class Source;
+class TestDomItem;
class Version;
-class DomTop;
-class DomEnvironment;
-class DomUniverse;
-class Subpath;
-class ObserversTrie;
+namespace ScriptElements {
+class BlockStatement;
+class IdentifierExpression;
+class Literal;
+class ForStatement;
+class IfStatement;
+class BinaryExpression;
+class VariableDeclaration;
+class VariableDeclarationEntry;
+class GenericScriptElement;
+// TODO: add new script classes here, as qqmldomitem_p.h cannot include qqmldomscriptelements_p.h
+// without creating circular dependencies
+class ReturnStatement;
+
+} // end namespace ScriptElements
} // end namespace Dom
} // end namespace QQmlJS
diff --git a/src/qmldom/qqmldom_global.h b/src/qmldom/qqmldom_global.h
index d76f6efeac..e0d343ab44 100644
--- a/src/qmldom/qqmldom_global.h
+++ b/src/qmldom/qqmldom_global.h
@@ -1,40 +1,6 @@
-/****************************************************************************
-**
-** 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$
-**/
+// Copyright (C) 2020 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+
#ifndef QMLDOM_GLOBAL_H
#define QMLDOM_GLOBAL_H
diff --git a/src/qmldom/qqmldom_utils.cpp b/src/qmldom/qqmldom_utils.cpp
new file mode 100644
index 0000000000..a7c985644a
--- /dev/null
+++ b/src/qmldom/qqmldom_utils.cpp
@@ -0,0 +1,63 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+
+#include "qqmldom_utils_p.h"
+#include <QtCore/qdir.h>
+#include <QtCore/qdiriterator.h>
+#include <QtCore/qstring.h>
+#include <QtCore/qmetaobject.h>
+#include <QtCore/qcbormap.h>
+
+QT_BEGIN_NAMESPACE
+
+Q_LOGGING_CATEGORY(QQmlJSDomImporting, "qt.qqmljsdom.importing")
+
+namespace QQmlJS {
+namespace Dom {
+
+using namespace Qt::StringLiterals;
+
+QStringList resourceFilesFromBuildFolders(const QStringList &buildFolders)
+{
+ QStringList result;
+ for (const QString &path : buildFolders) {
+ QDir dir(path);
+ if (!dir.cd(u".rcc"_s))
+ continue;
+
+ QDirIterator it(dir.canonicalPath(), QStringList{ u"*.qrc"_s }, QDir::Files,
+ QDirIterator::Subdirectories);
+ while (it.hasNext()) {
+ result.append(it.next());
+ }
+ }
+ return result;
+}
+
+static QMetaEnum regionEnum = QMetaEnum::fromType<FileLocationRegion>();
+
+QString fileLocationRegionName(FileLocationRegion region)
+{
+ return QString::fromLatin1(regionEnum.key(region));
+}
+
+FileLocationRegion fileLocationRegionValue(QStringView region)
+{
+ return static_cast<FileLocationRegion>(regionEnum.keyToValue(region.toLatin1()));
+}
+
+QCborValue sourceLocationToQCborValue(QQmlJS::SourceLocation loc)
+{
+ QCborMap res({
+ {QStringLiteral(u"offset"), loc.offset},
+ {QStringLiteral(u"length"), loc.length},
+ {QStringLiteral(u"startLine"), loc.startLine},
+ {QStringLiteral(u"startColumn"), loc.startColumn}
+ });
+ return res;
+}
+
+} // namespace Dom
+}; // namespace QQmlJS
+
+QT_END_NAMESPACE
diff --git a/src/qmldom/qqmldom_utils_p.h b/src/qmldom/qqmldom_utils_p.h
new file mode 100644
index 0000000000..6fcdb5fe10
--- /dev/null
+++ b/src/qmldom/qqmldom_utils_p.h
@@ -0,0 +1,53 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+
+#ifndef QQMLDOM_UTILS_P_H
+#define QQMLDOM_UTILS_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 <QtCore/qglobal.h>
+#include "qqmldom_fwd_p.h"
+#include "qqmldomconstants_p.h"
+#include <QtQml/private/qqmljssourcelocation_p.h>
+#include <QtCore/qstringlist.h>
+#include <QtCore/qloggingcategory.h>
+#include <QtCore/qcborvalue.h>
+
+QT_BEGIN_NAMESPACE
+
+Q_DECLARE_LOGGING_CATEGORY(QQmlJSDomImporting);
+
+template<class... Ts>
+struct qOverloadedVisitor : Ts...
+{
+ using Ts::operator()...;
+};
+template<class... Ts>
+qOverloadedVisitor(Ts...) -> qOverloadedVisitor<Ts...>;
+
+namespace QQmlJS {
+namespace Dom {
+
+QStringList resourceFilesFromBuildFolders(const QStringList &buildFolders);
+
+QString fileLocationRegionName(FileLocationRegion region);
+FileLocationRegion fileLocationRegionValue(QStringView region);
+
+QCborValue sourceLocationToQCborValue(SourceLocation loc);
+
+} // namespace Dom
+}; // namespace QQmlJS
+
+QT_END_NAMESPACE
+
+#endif // QQMLDOM_UTILS_P_H
diff --git a/src/qmldom/qqmldomastcreator.cpp b/src/qmldom/qqmldomastcreator.cpp
new file mode 100644
index 0000000000..c22a50ccfd
--- /dev/null
+++ b/src/qmldom/qqmldomastcreator.cpp
@@ -0,0 +1,3031 @@
+// Copyright (C) 2021 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+
+#include "qqmldomastcreator_p.h"
+#include "qqmldomconstants_p.h"
+#include "qqmldomelements_p.h"
+#include "qqmldomitem_p.h"
+#include "qqmldompath_p.h"
+#include "qqmldomscriptelements_p.h"
+#include "qqmldomtop_p.h"
+#include "qqmldomerrormessage_p.h"
+#include "qqmldomastdumper_p.h"
+#include "qqmldomattachedinfo_p.h"
+#include "qqmldomastcreator_p.h"
+#include "qqmldom_utils_p.h"
+
+#include <QtQml/private/qqmljsast_p.h>
+#include <QtQmlCompiler/private/qqmljsutils_p.h>
+
+#include <QtCore/QDir>
+#include <QtCore/QFileInfo>
+#include <QtCore/QScopeGuard>
+#include <QtCore/QLoggingCategory>
+
+#include <memory>
+#include <optional>
+#include <type_traits>
+#include <variant>
+#include <vector>
+
+static Q_LOGGING_CATEGORY(creatorLog, "qt.qmldom.astcreator", QtWarningMsg);
+
+/*
+ Avoid crashing on files with JS-elements that are not implemented yet.
+ Might be removed (definition + usages) once all script elements are implemented.
+*/
+#define Q_SCRIPTELEMENT_DISABLE() \
+ do { \
+ qDebug() << "Could not construct the JS DOM at" << __FILE__ << ":" << __LINE__ \
+ << ", skipping JS elements..."; \
+ disableScriptElements(); \
+ } while (false)
+
+#define Q_SCRIPTELEMENT_EXIT_IF(check) \
+ do { \
+ if (m_enableScriptExpressions && (check)) { \
+ Q_SCRIPTELEMENT_DISABLE(); \
+ return; \
+ } \
+ } while (false)
+
+QT_BEGIN_NAMESPACE
+namespace QQmlJS {
+namespace Dom {
+
+using namespace AST;
+
+template<typename K, typename V>
+V *valueFromMultimap(QMultiMap<K, V> &mmap, const K &key, index_type idx)
+{
+ if (idx < 0)
+ return nullptr;
+ auto it = mmap.find(key);
+ auto end = mmap.end();
+ if (it == end)
+ return nullptr;
+ auto it2 = it;
+ index_type nEl = 0;
+ while (it2 != end && it2.key() == key) {
+ ++it2;
+ ++nEl;
+ }
+ if (nEl <= idx)
+ return nullptr;
+ for (index_type i = idx + 1; i < nEl; ++i)
+ ++it;
+ return &(*it);
+}
+
+static ErrorGroups astParseErrors()
+{
+ static ErrorGroups errs = { { NewErrorGroup("Dom"), NewErrorGroup("QmlFile"),
+ NewErrorGroup("Parsing") } };
+ return errs;
+}
+
+static QString toString(const UiQualifiedId *qualifiedId, QChar delimiter = QLatin1Char('.'))
+{
+ QString result;
+
+ for (const UiQualifiedId *iter = qualifiedId; iter; iter = iter->next) {
+ if (iter != qualifiedId)
+ result += delimiter;
+
+ result += iter->name;
+ }
+
+ return result;
+}
+
+static QString typeToString(AST::Type *t)
+{
+ Q_ASSERT(t);
+ QString res = toString(t->typeId);
+
+ if (UiQualifiedId *arg = t->typeArgument)
+ res += u'<' + toString(arg) + u'>';
+
+ return res;
+}
+
+SourceLocation combineLocations(SourceLocation s1, SourceLocation s2)
+{
+ return combine(s1, s2);
+}
+
+SourceLocation combineLocations(Node *n)
+{
+ return combineLocations(n->firstSourceLocation(), n->lastSourceLocation());
+}
+
+static ScriptElementVariant wrapIntoFieldMemberExpression(const ScriptElementVariant &left,
+ const SourceLocation &dotToken,
+ const ScriptElementVariant &right)
+{
+ SourceLocation s1, s2;
+ left.visitConst([&s1](auto &&el) { s1 = el->mainRegionLocation(); });
+ right.visitConst([&s2](auto &&el) { s2 = el->mainRegionLocation(); });
+
+ auto result = std::make_shared<ScriptElements::BinaryExpression>(s1, s2);
+ result->addLocation(OperatorTokenRegion, dotToken);
+ result->setOp(ScriptElements::BinaryExpression::FieldMemberAccess);
+ result->setLeft(left);
+ result->setRight(right);
+ return ScriptElementVariant::fromElement(result);
+};
+
+/*!
+ \internal
+ Creates a FieldMemberExpression if the qualified id has dots.
+*/
+static ScriptElementVariant
+fieldMemberExpressionForQualifiedId(const AST::UiQualifiedId *qualifiedId)
+{
+ ScriptElementVariant bindable;
+ bool first = true;
+ for (auto exp = qualifiedId; exp; exp = exp->next) {
+ const SourceLocation identifierLoc = exp->identifierToken;
+ auto id = std::make_shared<ScriptElements::IdentifierExpression>(identifierLoc);
+ id->setName(exp->name);
+ if (first) {
+ first = false;
+ bindable = ScriptElementVariant::fromElement(id);
+ continue;
+ }
+ bindable = wrapIntoFieldMemberExpression(bindable, exp->dotToken,
+ ScriptElementVariant::fromElement(id));
+ }
+
+ return bindable;
+}
+
+QQmlDomAstCreator::QmlStackElement &QQmlDomAstCreator::currentQmlObjectOrComponentEl(int idx)
+{
+ Q_ASSERT_X(idx < nodeStack.size() && idx >= 0, "currentQmlObjectOrComponentEl",
+ "Stack does not contain enough elements!");
+ int i = nodeStack.size() - idx;
+ while (i-- > 0) {
+ DomType k = nodeStack.at(i).item.kind;
+ if (k == DomType::QmlObject || k == DomType::QmlComponent)
+ return nodeStack[i];
+ }
+ Q_ASSERT_X(false, "currentQmlObjectEl", "No QmlObject or component in stack");
+ return nodeStack.last();
+}
+
+QQmlDomAstCreator::QmlStackElement &QQmlDomAstCreator::currentNodeEl(int i)
+{
+ Q_ASSERT_X(i < nodeStack.size() && i >= 0, "currentNode", "Stack does not contain element!");
+ return nodeStack[nodeStack.size() - i - 1];
+}
+
+QQmlDomAstCreator::ScriptStackElement &QQmlDomAstCreator::currentScriptNodeEl(int i)
+{
+ Q_ASSERT_X(i < scriptNodeStack.size() && i >= 0, "currentNode",
+ "Stack does not contain element!");
+ return scriptNodeStack[scriptNodeStack.size() - i - 1];
+}
+
+QQmlDomAstCreator::DomValue &QQmlDomAstCreator::currentNode(int i)
+{
+ Q_ASSERT_X(i < nodeStack.size() && i >= 0, "currentNode",
+ "Stack does not contain element!");
+ return nodeStack[nodeStack.size() - i - 1].item;
+}
+
+void QQmlDomAstCreator::removeCurrentNode(std::optional<DomType> expectedType)
+{
+ Q_ASSERT_X(!nodeStack.isEmpty(), className, "popCurrentNode() without any node");
+ if (expectedType)
+ Q_ASSERT(nodeStack.last().item.kind == *expectedType);
+ nodeStack.removeLast();
+}
+
+void QQmlDomAstCreator::removeCurrentScriptNode(std::optional<DomType> expectedType)
+{
+ Q_SCRIPTELEMENT_EXIT_IF(scriptNodeStack.isEmpty());
+ Q_ASSERT_X(!scriptNodeStack.isEmpty(), className,
+ "popCurrentScriptNode() without any node");
+ if (expectedType)
+ Q_ASSERT(scriptNodeStack.last().kind == *expectedType);
+ scriptNodeStack.removeLast();
+}
+
+/*!
+ \internal
+ Prepares a script element DOM representation such that it can be used inside a QML DOM element.
+ This recursively sets the pathFromOwner and creates the FileLocations::Tree for all children of
+ element.
+
+ Beware that pathFromOwner is appended to ownerFileLocations when creating the FileLocations!
+
+ Make sure to add, for each of its use, a test in tst_qmldomitem:finalizeScriptExpressions, as
+ using a wrong pathFromOwner and/or a wrong base might lead to bugs hard to debug and spurious
+ crashes.
+ */
+const ScriptElementVariant &
+QQmlDomAstCreator::finalizeScriptExpression(const ScriptElementVariant &element, const Path &pathFromOwner,
+ const FileLocations::Tree &ownerFileLocations)
+{
+ auto e = element.base();
+ Q_ASSERT(e);
+
+ qCDebug(creatorLog) << "Finalizing script expression with path:"
+ << ownerFileLocations->canonicalPathForTesting().append(
+ pathFromOwner.toString());
+ e->updatePathFromOwner(pathFromOwner);
+ e->createFileLocations(ownerFileLocations);
+ return element;
+}
+
+FileLocations::Tree QQmlDomAstCreator::createMap(const FileLocations::Tree &base, const Path &p, AST::Node *n)
+{
+ FileLocations::Tree res = FileLocations::ensure(base, p, AttachedInfo::PathType::Relative);
+ if (n)
+ FileLocations::addRegion(res, MainRegion, combineLocations(n));
+ return res;
+}
+
+FileLocations::Tree QQmlDomAstCreator::createMap(DomType k, const Path &p, AST::Node *n)
+{
+ Path relative;
+ FileLocations::Tree base;
+ switch (k) {
+ case DomType::QmlObject:
+ switch (currentNode().kind) {
+ case DomType::QmlObject:
+ case DomType::QmlComponent:
+ case DomType::PropertyDefinition:
+ case DomType::Binding:
+ case DomType::Id:
+ case DomType::MethodInfo:
+ break;
+ default:
+ qCWarning(domLog) << "unexpected type" << domTypeToString(currentNode().kind);
+ Q_UNREACHABLE();
+ }
+ base = currentNodeEl().fileLocations;
+ if (p.length() > 2) {
+ Path p2 = p[p.length() - 2];
+ if (p2.headKind() == Path::Kind::Field
+ && (p2.checkHeadName(Fields::children) || p2.checkHeadName(Fields::objects)
+ || p2.checkHeadName(Fields::value) || p2.checkHeadName(Fields::annotations)
+ || p2.checkHeadName(Fields::children)))
+ relative = p.mid(p.length() - 2, 2);
+ else if (p.last().checkHeadName(Fields::value)
+ && p.last().headKind() == Path::Kind::Field)
+ relative = p.last();
+ else {
+ qCWarning(domLog) << "unexpected path to QmlObject in createMap" << p;
+ Q_UNREACHABLE();
+ }
+ } else {
+ qCWarning(domLog) << "unexpected path to QmlObject in createMap" << p;
+ Q_UNREACHABLE();
+ }
+ break;
+ case DomType::EnumItem:
+ relative = p;
+ base = currentNodeEl().fileLocations;
+ break;
+ case DomType::QmlComponent:
+ case DomType::Pragma:
+ case DomType::Import:
+ case DomType::Id:
+ case DomType::EnumDecl:
+ relative = p;
+ base = rootMap;
+ break;
+ case DomType::Binding:
+ case DomType::PropertyDefinition:
+ case DomType::MethodInfo:
+ base = currentEl<QmlObject>().fileLocations;
+ if (p.length() > 3)
+ relative = p.mid(p.length() - 3, 3);
+ else
+ relative = p;
+ break;
+
+ default:
+ qCWarning(domLog) << "Unexpected type in createMap:" << domTypeToString(k);
+ Q_UNREACHABLE();
+ break;
+ }
+ return createMap(base, relative, n);
+}
+
+QQmlDomAstCreator::QQmlDomAstCreator(const MutableDomItem &qmlFile)
+ : qmlFile(qmlFile),
+ qmlFilePtr(qmlFile.ownerAs<QmlFile>()),
+ rootMap(qmlFilePtr->fileLocationsTree())
+{
+}
+
+bool QQmlDomAstCreator::visit(UiProgram *program)
+{
+ QFileInfo fInfo(qmlFile.canonicalFilePath());
+ QString componentName = fInfo.baseName();
+ QmlComponent *cPtr;
+ Path p = qmlFilePtr->addComponent(QmlComponent(componentName), AddOption::KeepExisting,
+ &cPtr);
+ MutableDomItem newC(qmlFile.item(), p);
+ Q_ASSERT_X(newC.item(), className, "could not recover component added with addComponent");
+ // QmlFile region == Component region == program span
+ // we hide the component span because the component s written after the imports
+ FileLocations::addRegion(rootMap, MainRegion, combineLocations(program));
+ pushEl(p, *cPtr, program);
+
+ auto envPtr = qmlFile.environment().ownerAs<DomEnvironment>();
+ const bool loadDependencies =
+ !envPtr->options().testFlag(DomEnvironment::Option::NoDependencies);
+ // add implicit directory import and load them in the Dom
+ if (!fInfo.canonicalPath().isEmpty()) {
+ Import selfDirImport(QmlUri::fromDirectoryString(fInfo.canonicalPath()));
+ selfDirImport.implicit = true;
+ qmlFilePtr->addImport(selfDirImport);
+
+ if (loadDependencies) {
+ const QString currentFileDir =
+ QFileInfo(qmlFile.canonicalFilePath()).dir().canonicalPath();
+ envPtr->loadFile(FileToLoad::fromFileSystem(
+ envPtr, selfDirImport.uri.absoluteLocalPath(currentFileDir)),
+ DomItem::Callback(), DomType::QmlDirectory);
+ }
+ }
+ // add implicit imports from the environment (QML, QtQml for example) and load them in the Dom
+ for (Import i : qmlFile.environment().ownerAs<DomEnvironment>()->implicitImports()) {
+ i.implicit = true;
+ qmlFilePtr->addImport(i);
+
+ if (loadDependencies)
+ envPtr->loadModuleDependency(i.uri.moduleUri(), i.version, DomItem::Callback());
+ }
+ if (m_loadFileLazily && loadDependencies) {
+ envPtr->loadPendingDependencies();
+ envPtr->commitToBase(qmlFile.environment().item());
+ }
+
+ return true;
+}
+
+void QQmlDomAstCreator::endVisit(AST::UiProgram *)
+{
+ MutableDomItem newC = qmlFile.path(currentNodeEl().path);
+ QmlComponent &comp = current<QmlComponent>();
+ for (const Pragma &p : qmlFilePtr->pragmas()) {
+ if (p.name.compare(u"singleton", Qt::CaseInsensitive) == 0) {
+ comp.setIsSingleton(true);
+ comp.setIsCreatable(false); // correct?
+ }
+ }
+ *newC.mutableAs<QmlComponent>() = comp;
+ removeCurrentNode(DomType::QmlComponent);
+ Q_ASSERT_X(nodeStack.isEmpty(), className, "ui program did not finish node stack");
+}
+
+bool QQmlDomAstCreator::visit(UiPragma *el)
+{
+ QStringList valueList;
+ for (auto t = el->values; t; t = t->next)
+ valueList << t->value.toString();
+
+ auto fileLocation = createMap(
+ DomType::Pragma, qmlFilePtr->addPragma(Pragma(el->name.toString(), valueList)), el);
+ FileLocations::addRegion(fileLocation, PragmaKeywordRegion, el->pragmaToken);
+ FileLocations::addRegion(fileLocation, IdentifierRegion, el->pragmaIdToken);
+ if (el->colonToken.isValid()) {
+ FileLocations::addRegion(fileLocation, ColonTokenRegion, el->colonToken);
+ }
+ int i = 0;
+ for (auto t = el->values; t; t = t->next) {
+ auto subMap = createMap(fileLocation, Path().field(Fields::values).index(i), t);
+ FileLocations::addRegion(subMap, PragmaValuesRegion, t->location);
+ ++i;
+ }
+
+ return true;
+}
+
+bool QQmlDomAstCreator::visit(UiImport *el)
+{
+ Version v(Version::Latest, Version::Latest);
+ if (el->version && el->version->version.hasMajorVersion())
+ v.majorVersion = el->version->version.majorVersion();
+ if (el->version && el->version->version.hasMinorVersion())
+ v.minorVersion = el->version->version.minorVersion();
+
+ auto envPtr = qmlFile.environment().ownerAs<DomEnvironment>();
+ const bool loadDependencies =
+ !envPtr->options().testFlag(DomEnvironment::Option::NoDependencies);
+ FileLocations::Tree fileLocation;
+ if (el->importUri != nullptr) {
+ const Import import =
+ Import::fromUriString(toString(el->importUri), v, el->importId.toString());
+ fileLocation = createMap(DomType::Import, qmlFilePtr->addImport(import), el);
+
+ if (loadDependencies) {
+ envPtr->loadModuleDependency(import.uri.moduleUri(), import.version,
+ DomItem::Callback());
+ }
+ FileLocations::addRegion(fileLocation, ImportUriRegion, combineLocations(el->importUri));
+ } else {
+ const Import import =
+ Import::fromFileString(el->fileName.toString(), el->importId.toString());
+ fileLocation = createMap(DomType::Import, qmlFilePtr->addImport(import), el);
+
+ if (loadDependencies) {
+ const QString currentFileDir =
+ QFileInfo(qmlFile.canonicalFilePath()).dir().canonicalPath();
+ envPtr->loadFile(FileToLoad::fromFileSystem(
+ envPtr, import.uri.absoluteLocalPath(currentFileDir)),
+ DomItem::Callback(), DomType::QmlDirectory);
+ }
+ FileLocations::addRegion(fileLocation, ImportUriRegion, el->fileNameToken);
+ }
+ if (m_loadFileLazily && loadDependencies) {
+ envPtr->loadPendingDependencies();
+ envPtr->commitToBase(qmlFile.environment().item());
+ }
+
+ if (el->importToken.isValid())
+ FileLocations::addRegion(fileLocation, ImportTokenRegion, el->importToken);
+
+ if (el->asToken.isValid())
+ FileLocations::addRegion(fileLocation, AsTokenRegion, el->asToken);
+
+ if (el->importIdToken.isValid())
+ FileLocations::addRegion(fileLocation, IdNameRegion, el->importIdToken);
+
+ if (el->version)
+ FileLocations::addRegion(fileLocation, VersionRegion, combineLocations(el->version));
+
+
+ return true;
+}
+
+bool QQmlDomAstCreator::visit(AST::UiPublicMember *el)
+{
+ switch (el->type) {
+ case AST::UiPublicMember::Signal: {
+ MethodInfo m;
+ m.name = el->name.toString();
+ m.typeName = toString(el->memberType);
+ m.isReadonly = el->isReadonly();
+ m.access = MethodInfo::Public;
+ m.methodType = MethodInfo::Signal;
+ m.isList = el->typeModifier == QLatin1String("list");
+ MethodInfo *mPtr;
+ Path p = current<QmlObject>().addMethod(m, AddOption::KeepExisting, &mPtr);
+ pushEl(p, *mPtr, el);
+ FileLocations::addRegion(nodeStack.last().fileLocations, SignalKeywordRegion,
+ el->propertyToken());
+ FileLocations::addRegion(nodeStack.last().fileLocations, IdentifierRegion,
+ el->identifierToken);
+ MethodInfo &mInfo = std::get<MethodInfo>(currentNode().value);
+ AST::UiParameterList *args = el->parameters;
+ while (args) {
+ MethodParameter param;
+ param.name = args->name.toString();
+ param.typeName = args->type ? args->type->toString() : QString();
+ index_type idx = index_type(mInfo.parameters.size());
+ mInfo.parameters.append(param);
+ auto argLocs = FileLocations::ensure(nodeStack.last().fileLocations,
+ Path::Field(Fields::parameters).index(idx),
+ AttachedInfo::PathType::Relative);
+ FileLocations::addRegion(argLocs, MainRegion, combineLocations(args));
+ FileLocations::addRegion(argLocs, IdentifierRegion, args->identifierToken);
+ if (args->type)
+ FileLocations::addRegion(argLocs, TypeIdentifierRegion, args->propertyTypeToken);
+ args = args->next;
+ }
+ break;
+ }
+ case AST::UiPublicMember::Property: {
+ PropertyDefinition p;
+ p.name = el->name.toString();
+ p.typeName = toString(el->memberType);
+ p.isReadonly = el->isReadonly();
+ p.isDefaultMember = el->isDefaultMember();
+ p.isRequired = el->isRequired();
+ p.isList = el->typeModifier == QLatin1String("list");
+ if (!el->typeModifier.isEmpty())
+ p.typeName = el->typeModifier.toString() + QChar(u'<') + p.typeName + QChar(u'>');
+ PropertyDefinition *pPtr;
+ Path pPathFromOwner =
+ current<QmlObject>().addPropertyDef(p, AddOption::KeepExisting, &pPtr);
+ if (m_enableScriptExpressions) {
+ auto qmlObjectType = makeGenericScriptElement(el->memberType, DomType::ScriptType);
+ qmlObjectType->insertChild(Fields::typeName,
+ fieldMemberExpressionForQualifiedId(el->memberType));
+ pPtr->setNameIdentifiers(finalizeScriptExpression(
+ ScriptElementVariant::fromElement(qmlObjectType),
+ pPathFromOwner.field(Fields::nameIdentifiers), rootMap));
+ // skip binding identifiers of the binding inside the property definition, if there is
+ // one
+ m_skipBindingIdentifiers = el->binding;
+ }
+ pushEl(pPathFromOwner, *pPtr, el);
+ FileLocations::addRegion(nodeStack.last().fileLocations, PropertyKeywordRegion,
+ el->propertyToken());
+ FileLocations::addRegion(nodeStack.last().fileLocations, IdentifierRegion,
+ el->identifierToken);
+ FileLocations::addRegion(nodeStack.last().fileLocations, TypeIdentifierRegion,
+ el->typeToken);
+ FileLocations::addRegion(nodeStack.last().fileLocations, ColonTokenRegion, el->colonToken);
+ if (p.name == u"id")
+ qmlFile.addError(std::move(astParseErrors()
+ .warning(tr("id is a special attribute, that should not be "
+ "used as property name"))
+ .withPath(currentNodeEl().path)));
+ if (p.isDefaultMember) {
+ FileLocations::addRegion(nodeStack.last().fileLocations, DefaultKeywordRegion,
+ el->defaultToken());
+ }
+ if (p.isRequired) {
+ FileLocations::addRegion(nodeStack.last().fileLocations, RequiredKeywordRegion,
+ el->requiredToken());
+ }
+ if (p.isReadonly) {
+ FileLocations::addRegion(nodeStack.last().fileLocations, ReadonlyKeywordRegion,
+ el->readonlyToken());
+ }
+ if (el->statement) {
+ BindingType bType = BindingType::Normal;
+ SourceLocation loc = combineLocations(el->statement);
+ QStringView code = qmlFilePtr->code();
+
+ auto script = std::make_shared<ScriptExpression>(
+ code.mid(loc.offset, loc.length), qmlFilePtr->engine(), el->statement,
+ qmlFilePtr->astComments(), ScriptExpression::ExpressionType::BindingExpression,
+ loc);
+ Binding *bPtr;
+ Path bPathFromOwner = current<QmlObject>().addBinding(Binding(p.name, script, bType),
+ AddOption::KeepExisting, &bPtr);
+ FileLocations::Tree bLoc = createMap(DomType::Binding, bPathFromOwner, el);
+ FileLocations::addRegion(bLoc, ColonTokenRegion, el->colonToken);
+ FileLocations::Tree valueLoc = FileLocations::ensure(bLoc, Path::Field(Fields::value),
+ AttachedInfo::PathType::Relative);
+ FileLocations::addRegion(valueLoc, MainRegion, combineLocations(el->statement));
+ // push it also: its needed in endVisit to add the scriptNode to it
+ // do not use pushEl to avoid recreating the already created "bLoc" Map
+ nodeStack.append({ bPathFromOwner, *bPtr, bLoc });
+ }
+ break;
+ }
+ }
+ return true;
+}
+
+void QQmlDomAstCreator::endVisit(AST::UiPublicMember *el)
+{
+ if (auto &lastEl = currentNode(); lastEl.kind == DomType::Binding) {
+ Binding &b = std::get<Binding>(lastEl.value);
+ if (m_enableScriptExpressions
+ && (scriptNodeStack.size() != 1 || scriptNodeStack.last().isList())) {
+ Q_SCRIPTELEMENT_DISABLE();
+ }
+ if (m_enableScriptExpressions) {
+ b.scriptExpressionValue()->setScriptElement(finalizeScriptExpression(
+ currentScriptNodeEl().takeVariant(), Path().field(Fields::scriptElement),
+ FileLocations::ensure(currentNodeEl().fileLocations,
+ Path().field(Fields::value))));
+ removeCurrentScriptNode({});
+ }
+
+ QmlObject &containingObject = current<QmlObject>();
+ Binding *bPtr =
+ valueFromMultimap(containingObject.m_bindings, b.name(), currentIndex());
+ Q_ASSERT(bPtr);
+ removeCurrentNode({});
+ }
+ Node::accept(el->parameters, this);
+ loadAnnotations(el);
+ if ((el->binding || el->statement)
+ && nodeStack.last().item.kind == DomType::PropertyDefinition) {
+ PropertyDefinition &pDef = std::get<PropertyDefinition>(nodeStack.last().item.value);
+ if (!pDef.annotations.isEmpty()) {
+ QmlObject duplicate;
+ duplicate.setName(QLatin1String("duplicate"));
+ QmlObject &obj = current<QmlObject>();
+ auto it = obj.m_bindings.find(pDef.name);
+ if (it != obj.m_bindings.end()) {
+ for (QmlObject ann : pDef.annotations) {
+ ann.addAnnotation(duplicate);
+ it->addAnnotation(currentEl<QmlObject>()
+ .path.field(Fields::bindings)
+ .key(pDef.name)
+ .index(obj.m_bindings.values(pDef.name).size() - 1),
+ ann);
+ }
+ }
+ }
+ }
+ QmlObject &obj = current<QmlObject>();
+ QmlStackElement &sEl = nodeStack.last();
+ switch (sEl.item.kind) {
+ case DomType::PropertyDefinition: {
+ PropertyDefinition pDef = std::get<PropertyDefinition>(sEl.item.value);
+ PropertyDefinition *pDefPtr =
+ valueFromMultimap(obj.m_propertyDefs, pDef.name, sEl.path.last().headIndex());
+ Q_ASSERT(pDefPtr);
+ *pDefPtr = pDef;
+ } break;
+ case DomType::MethodInfo: {
+ MethodInfo m = std::get<MethodInfo>(sEl.item.value);
+ MethodInfo *mPtr = valueFromMultimap(obj.m_methods, m.name, sEl.path.last().headIndex());
+ Q_ASSERT(mPtr);
+ *mPtr = m;
+ } break;
+ default:
+ Q_UNREACHABLE();
+ }
+ removeCurrentNode({});
+}
+
+bool QQmlDomAstCreator::visit(AST::FunctionDeclaration *fDef)
+{
+ const QStringView code(qmlFilePtr->code());
+ MethodInfo m;
+ m.name = fDef->name.toString();
+ if (AST::TypeAnnotation *tAnn = fDef->typeAnnotation) {
+ if (AST::Type *t = tAnn->type)
+ m.typeName = typeToString(t);
+ }
+ m.access = MethodInfo::Public;
+ m.methodType = MethodInfo::Method;
+
+ SourceLocation bodyLoc = fDef->body ? combineLocations(fDef->body)
+ : combineLocations(fDef->lbraceToken, fDef->rbraceToken);
+ SourceLocation methodLoc = combineLocations(fDef);
+ QStringView preCode = code.mid(methodLoc.begin(), bodyLoc.begin() - methodLoc.begin());
+ QStringView postCode = code.mid(bodyLoc.end(), methodLoc.end() - bodyLoc.end());
+ m.body = std::make_shared<ScriptExpression>(
+ code.mid(bodyLoc.offset, bodyLoc.length), qmlFilePtr->engine(), fDef->body,
+ qmlFilePtr->astComments(), ScriptExpression::ExpressionType::FunctionBody, bodyLoc, 0,
+ preCode, postCode);
+
+ if (fDef->typeAnnotation) {
+ SourceLocation typeLoc = combineLocations(fDef->typeAnnotation);
+ m.returnType = std::make_shared<ScriptExpression>(
+ code.mid(typeLoc.offset, typeLoc.length), qmlFilePtr->engine(),
+ fDef->typeAnnotation, qmlFilePtr->astComments(),
+ ScriptExpression::ExpressionType::ReturnType, typeLoc, 0, u"", u"");
+ }
+
+ MethodInfo *mPtr;
+ Path mPathFromOwner = current<QmlObject>().addMethod(m, AddOption::KeepExisting, &mPtr);
+ pushEl(mPathFromOwner, *mPtr,
+ fDef); // add at the start and use the normal recursive visit?
+ FileLocations::Tree &fLoc = nodeStack.last().fileLocations;
+ if (fDef->identifierToken.isValid())
+ FileLocations::addRegion(fLoc, IdentifierRegion, fDef->identifierToken);
+ auto bodyTree = FileLocations::ensure(fLoc, Path::Field(Fields::body),
+ AttachedInfo::PathType::Relative);
+ FileLocations::addRegion(bodyTree, MainRegion, bodyLoc);
+ if (fDef->functionToken.isValid())
+ FileLocations::addRegion(fLoc, FunctionKeywordRegion, fDef->functionToken);
+ if (fDef->lparenToken.length != 0)
+ FileLocations::addRegion(fLoc, LeftParenthesisRegion, fDef->lparenToken);
+ if (fDef->rparenToken.length != 0)
+ FileLocations::addRegion(fLoc, RightParenthesisRegion, fDef->rparenToken);
+ if (fDef->lbraceToken.length != 0)
+ FileLocations::addRegion(fLoc, LeftBraceRegion, fDef->lbraceToken);
+ if (fDef->rbraceToken.length != 0)
+ FileLocations::addRegion(fLoc, RightBraceRegion, fDef->rbraceToken);
+ if (fDef->typeAnnotation)
+ FileLocations::addRegion(fLoc, TypeIdentifierRegion, combineLocations(fDef->typeAnnotation->type));
+ MethodInfo &mInfo = std::get<MethodInfo>(currentNode().value);
+ AST::FormalParameterList *args = fDef->formals;
+ while (args) {
+ MethodParameter param;
+ param.name = args->element->bindingIdentifier.toString();
+ if (AST::TypeAnnotation *tAnn = args->element->typeAnnotation) {
+ if (AST::Type *t = tAnn->type)
+ param.typeName = typeToString(t);
+ }
+ if (args->element->initializer) {
+ SourceLocation loc = combineLocations(args->element->initializer);
+ auto script = std::make_shared<ScriptExpression>(
+ code.mid(loc.offset, loc.length), qmlFilePtr->engine(),
+ args->element->initializer, qmlFilePtr->astComments(),
+ ScriptExpression::ExpressionType::ArgInitializer, loc);
+ param.defaultValue = script;
+ }
+ if (args->element->type == AST::PatternElement::SpreadElement)
+ param.isRestElement = true;
+ SourceLocation parameterLoc = combineLocations(args->element);
+ param.value = std::make_shared<ScriptExpression>(
+ code.mid(parameterLoc.offset, parameterLoc.length), qmlFilePtr->engine(),
+ args->element, qmlFilePtr->astComments(),
+ ScriptExpression::ExpressionType::ArgumentStructure, parameterLoc);
+
+ index_type idx = index_type(mInfo.parameters.size());
+ mInfo.parameters.append(param);
+ auto argLocs = FileLocations::ensure(nodeStack.last().fileLocations,
+ Path::Field(Fields::parameters).index(idx),
+ AttachedInfo::PathType::Relative);
+ FileLocations::addRegion(argLocs, MainRegion, combineLocations(args));
+ if (args->element->identifierToken.isValid())
+ FileLocations::addRegion(argLocs, IdentifierRegion, args->element->identifierToken);
+ if (args->element->typeAnnotation)
+ FileLocations::addRegion(argLocs, TypeIdentifierRegion, combineLocations(args->element->typeAnnotation->type));
+ args = args->next;
+ }
+ return true;
+}
+
+bool QQmlDomAstCreator::visit(AST::UiSourceElement *el)
+{
+ if (!cast<FunctionDeclaration *>(el->sourceElement)) {
+ qCWarning(creatorLog) << "unhandled source el:" << static_cast<AST::Node *>(el);
+ Q_UNREACHABLE();
+ }
+ return true;
+}
+
+static void setFormalParameterKind(ScriptElementVariant &variant)
+{
+ if (auto data = variant.data()) {
+ if (auto genericElement =
+ std::get_if<std::shared_ptr<ScriptElements::GenericScriptElement>>(&*data)) {
+ (*genericElement)->setKind(DomType::ScriptFormalParameter);
+ }
+ }
+}
+
+void QQmlDomAstCreator::endVisit(AST::FunctionDeclaration *fDef)
+{
+ MethodInfo &m = std::get<MethodInfo>(currentNode().value);
+ const FileLocations::Tree bodyTree =
+ FileLocations::ensure(currentNodeEl().fileLocations, Path().field(Fields::body));
+ const Path bodyPath = Path().field(Fields::scriptElement);
+
+ if (!m_enableScriptExpressions)
+ return;
+
+ if (fDef->body) {
+ Q_SCRIPTELEMENT_EXIT_IF(scriptNodeStack.isEmpty());
+ if (currentScriptNodeEl().isList()) {
+ // It is more intuitive to have functions with a block as a body instead of a
+ // list.
+ auto body = std::make_shared<ScriptElements::BlockStatement>(
+ combineLocations(fDef->lbraceToken, fDef->rbraceToken));
+ body->setStatements(currentScriptNodeEl().takeList());
+ if (auto semanticScope = body->statements().semanticScope())
+ body->setSemanticScope(semanticScope);
+ m.body->setScriptElement(finalizeScriptExpression(
+ ScriptElementVariant::fromElement(body), bodyPath, bodyTree));
+ } else {
+ m.body->setScriptElement(finalizeScriptExpression(
+ currentScriptNodeEl().takeVariant(), bodyPath, bodyTree));
+ }
+ removeCurrentScriptNode({});
+ } else {
+ // for convenience purposes: insert an empty BlockStatement
+ auto body = std::make_shared<ScriptElements::BlockStatement>(
+ combineLocations(fDef->lbraceToken, fDef->rbraceToken));
+ m.body->setScriptElement(finalizeScriptExpression(ScriptElementVariant::fromElement(body),
+ bodyPath, bodyTree));
+ }
+
+ if (fDef->typeAnnotation) {
+ auto argLoc = FileLocations::ensure(nodeStack.last().fileLocations,
+ Path().field(Fields::returnType),
+ AttachedInfo::PathType::Relative);
+ const Path pathToReturnType = Path().field(Fields::scriptElement);
+
+ Q_SCRIPTELEMENT_EXIT_IF(scriptNodeStack.isEmpty() || scriptNodeStack.last().isList());
+ ScriptElementVariant variant = currentScriptNodeEl().takeVariant();
+ finalizeScriptExpression(variant, pathToReturnType, argLoc);
+ m.returnType->setScriptElement(variant);
+ removeCurrentScriptNode({});
+ }
+ std::vector<FormalParameterList *> reversedInitializerExpressions;
+ for (auto it = fDef->formals; it; it = it->next) {
+ reversedInitializerExpressions.push_back(it);
+ }
+ const size_t size = reversedInitializerExpressions.size();
+ for (size_t idx = size - 1; idx < size; --idx) {
+ auto argLoc = FileLocations::ensure(
+ nodeStack.last().fileLocations,
+ Path().field(Fields::parameters).index(idx).field(Fields::value),
+ AttachedInfo::PathType::Relative);
+ const Path pathToArgument = Path().field(Fields::scriptElement);
+
+ Q_SCRIPTELEMENT_EXIT_IF(scriptNodeStack.isEmpty() || scriptNodeStack.last().isList());
+ ScriptElementVariant variant = currentScriptNodeEl().takeVariant();
+ setFormalParameterKind(variant);
+ finalizeScriptExpression(variant, pathToArgument, argLoc);
+ m.parameters[idx].value->setScriptElement(variant);
+ removeCurrentScriptNode({});
+ }
+
+ // there should be no more uncollected script elements
+ if (m_enableScriptExpressions && !scriptNodeStack.empty()) {
+ Q_SCRIPTELEMENT_DISABLE();
+ }
+}
+
+void QQmlDomAstCreator::endVisit(AST::UiSourceElement *el)
+{
+ MethodInfo &m = std::get<MethodInfo>(currentNode().value);
+ loadAnnotations(el);
+ QmlObject &obj = current<QmlObject>();
+ MethodInfo *mPtr =
+ valueFromMultimap(obj.m_methods, m.name, nodeStack.last().path.last().headIndex());
+ Q_ASSERT(mPtr);
+ *mPtr = m;
+ removeCurrentNode(DomType::MethodInfo);
+}
+
+bool QQmlDomAstCreator::visit(AST::UiObjectDefinition *el)
+{
+ QmlObject scope;
+ scope.setName(toString(el->qualifiedTypeNameId));
+ scope.addPrototypePath(Paths::lookupTypePath(scope.name()));
+ QmlObject *sPtr = nullptr;
+ Path sPathFromOwner;
+ if (!arrayBindingLevels.isEmpty() && nodeStack.size() == arrayBindingLevels.last()) {
+ if (currentNode().kind == DomType::Binding) {
+ QList<QmlObject> *vals = std::get<Binding>(currentNode().value).arrayValue();
+ if (vals) {
+ int idx = vals->size();
+ vals->append(scope);
+ sPathFromOwner = currentNodeEl().path.field(Fields::value).index(idx);
+ sPtr = &((*vals)[idx]);
+ sPtr->updatePathFromOwner(sPathFromOwner);
+ } else {
+ Q_ASSERT_X(false, className,
+ "expected an array binding with a valid QList<QmlScope> as value");
+ }
+ } else {
+ Q_ASSERT_X(false, className, "expected an array binding as last node on the stack");
+ }
+ } else {
+ DomValue &containingObject = currentQmlObjectOrComponentEl().item;
+ switch (containingObject.kind) {
+ case DomType::QmlComponent:
+ sPathFromOwner = std::get<QmlComponent>(containingObject.value).addObject(scope, &sPtr);
+ break;
+ case DomType::QmlObject:
+ sPathFromOwner = std::get<QmlObject>(containingObject.value).addChild(scope, &sPtr);
+ break;
+ default:
+ Q_UNREACHABLE();
+ }
+ Path pathFromContainingObject = sPathFromOwner.mid(currentNodeEl().path.length());
+ FileLocations::Tree fLoc =
+ FileLocations::ensure(currentNodeEl().fileLocations, pathFromContainingObject,
+ AttachedInfo::PathType::Relative);
+ FileLocations::addRegion(fLoc, IdentifierRegion,
+ el->qualifiedTypeNameId->identifierToken);
+ }
+ Q_ASSERT_X(sPtr, className, "could not recover new scope");
+
+ if (m_enableScriptExpressions) {
+ auto qmlObjectType = makeGenericScriptElement(el->qualifiedTypeNameId, DomType::ScriptType);
+ qmlObjectType->insertChild(Fields::typeName,
+ fieldMemberExpressionForQualifiedId(el->qualifiedTypeNameId));
+ sPtr->setNameIdentifiers(
+ finalizeScriptExpression(ScriptElementVariant::fromElement(qmlObjectType),
+ sPathFromOwner.field(Fields::nameIdentifiers), rootMap));
+ }
+ pushEl(sPathFromOwner, *sPtr, el);
+
+ if (m_enableScriptExpressions && el->initializer) {
+ FileLocations::addRegion(nodeStack.last().fileLocations, LeftBraceRegion,
+ el->initializer->lbraceToken);
+ FileLocations::addRegion(nodeStack.last().fileLocations, RightBraceRegion,
+ el->initializer->rbraceToken);
+ }
+ loadAnnotations(el);
+ return true;
+}
+
+void QQmlDomAstCreator::endVisit(AST::UiObjectDefinition *)
+{
+ QmlObject &obj = current<QmlObject>();
+ int idx = currentIndex();
+ if (!arrayBindingLevels.isEmpty() && nodeStack.size() == arrayBindingLevels.last() + 1) {
+ if (currentNode(1).kind == DomType::Binding) {
+ Binding &b = std::get<Binding>(currentNode(1).value);
+ QList<QmlObject> *vals = b.arrayValue();
+ Q_ASSERT_X(vals, className,
+ "expected an array binding with a valid QList<QmlScope> as value");
+ (*vals)[idx] = obj;
+ } else {
+ Q_ASSERT_X(false, className, "expected an array binding as last node on the stack");
+ }
+ } else {
+ DomValue &containingObject = currentNodeEl(1).item;
+ Path p = currentNodeEl().path;
+ switch (containingObject.kind) {
+ case DomType::QmlComponent:
+ if (p[p.length() - 2] == Path::Field(Fields::objects))
+ std::get<QmlComponent>(containingObject.value).m_objects[idx] = obj;
+ else
+ Q_UNREACHABLE();
+ break;
+ case DomType::QmlObject:
+ if (p[p.length() - 2] == Path::Field(Fields::children))
+ std::get<QmlObject>(containingObject.value).m_children[idx] = obj;
+ else
+ Q_UNREACHABLE();
+ break;
+ default:
+ Q_UNREACHABLE();
+ }
+ }
+ removeCurrentNode(DomType::QmlObject);
+}
+
+void QQmlDomAstCreator::setBindingIdentifiers(const Path &pathFromOwner,
+ const UiQualifiedId *identifiers, Binding *bindingPtr)
+{
+ const bool skipBindingIdentifiers = std::exchange(m_skipBindingIdentifiers, false);
+ if (!m_enableScriptExpressions || skipBindingIdentifiers)
+ return;
+
+ ScriptElementVariant bindable = fieldMemberExpressionForQualifiedId(identifiers);
+ bindingPtr->setBindingIdentifiers(finalizeScriptExpression(
+ bindable, pathFromOwner.field(Fields::bindingIdentifiers), rootMap));
+}
+
+bool QQmlDomAstCreator::visit(AST::UiObjectBinding *el)
+{
+ BindingType bType = (el->hasOnToken ? BindingType::OnBinding : BindingType::Normal);
+ QmlObject value;
+ value.setName(toString(el->qualifiedTypeNameId));
+ Binding *bPtr;
+ Path bPathFromOwner = current<QmlObject>().addBinding(
+ Binding(toString(el->qualifiedId), value, bType), AddOption::KeepExisting, &bPtr);
+ if (bPtr->name() == u"id")
+ qmlFile.addError(std::move(astParseErrors()
+ .warning(tr("id attributes should only be a lower case letter "
+ "followed by letters, numbers or underscore, "
+ "assuming they refer to an id property"))
+ .withPath(bPathFromOwner)));
+ setBindingIdentifiers(bPathFromOwner, el->qualifiedId, bPtr);
+
+ pushEl(bPathFromOwner, *bPtr, el);
+ FileLocations::addRegion(nodeStack.last().fileLocations, ColonTokenRegion, el->colonToken);
+ FileLocations::addRegion(nodeStack.last().fileLocations, IdentifierRegion, combineLocations(el->qualifiedId));
+ loadAnnotations(el);
+ QmlObject *objValue = bPtr->objectValue();
+ Q_ASSERT_X(objValue, className, "could not recover objectValue");
+ objValue->setName(toString(el->qualifiedTypeNameId));
+
+ if (m_enableScriptExpressions) {
+ auto qmlObjectType = makeGenericScriptElement(el->qualifiedTypeNameId, DomType::ScriptType);
+ qmlObjectType->insertChild(Fields::typeName,
+ fieldMemberExpressionForQualifiedId(el->qualifiedTypeNameId));
+ objValue->setNameIdentifiers(finalizeScriptExpression(
+ ScriptElementVariant::fromElement(qmlObjectType),
+ bPathFromOwner.field(Fields::value).field(Fields::nameIdentifiers), rootMap));
+ }
+
+ objValue->addPrototypePath(Paths::lookupTypePath(objValue->name()));
+ pushEl(bPathFromOwner.field(Fields::value), *objValue, el->initializer);
+ if (m_enableScriptExpressions && el->initializer) {
+ FileLocations::addRegion(nodeStack.last().fileLocations, LeftBraceRegion,
+ el->initializer->lbraceToken);
+ FileLocations::addRegion(nodeStack.last().fileLocations, RightBraceRegion,
+ el->initializer->rbraceToken);
+ }
+ return true;
+}
+
+void QQmlDomAstCreator::endVisit(AST::UiObjectBinding *)
+{
+ QmlObject &objValue = current<QmlObject>();
+ QmlObject &containingObj = current<QmlObject>(1);
+ Binding &b = std::get<Binding>(currentNode(1).value);
+ QmlObject *objPtr = b.objectValue();
+ Q_ASSERT(objPtr);
+ *objPtr = objValue;
+ index_type idx = currentNodeEl(1).path.last().headIndex();
+ Binding *bPtr = valueFromMultimap(containingObj.m_bindings, b.name(), idx);
+ Q_ASSERT(bPtr);
+ *bPtr = b;
+ removeCurrentNode(DomType::QmlObject);
+ removeCurrentNode(DomType::Binding);
+}
+
+bool QQmlDomAstCreator::visit(AST::UiScriptBinding *el)
+{
+ QStringView code = qmlFilePtr->code();
+ SourceLocation loc = combineLocations(el->statement);
+ auto script = std::make_shared<ScriptExpression>(
+ code.mid(loc.offset, loc.length), qmlFilePtr->engine(), el->statement,
+ qmlFilePtr->astComments(), ScriptExpression::ExpressionType::BindingExpression, loc);
+ Binding bindingV(toString(el->qualifiedId), script, BindingType::Normal);
+ Binding *bindingPtr = nullptr;
+ Id *idPtr = nullptr;
+ Path pathFromOwner;
+ if (bindingV.name() == u"id") {
+ Node *exp = script->ast();
+ if (ExpressionStatement *eStat = cast<ExpressionStatement *>(script->ast()))
+ exp = eStat->expression;
+ if (IdentifierExpression *iExp = cast<IdentifierExpression *>(exp)) {
+ QmlStackElement &containingObjectEl = currentEl<QmlObject>();
+ QmlObject &containingObject = std::get<QmlObject>(containingObjectEl.item.value);
+ QString idName = iExp->name.toString();
+ Id idVal(idName, qmlFile.canonicalPath().path(containingObject.pathFromOwner()));
+ idVal.value = script;
+ containingObject.setIdStr(idName);
+ FileLocations::addRegion(containingObjectEl.fileLocations, IdTokenRegion,
+ combineLocations(el->qualifiedId));
+ FileLocations::addRegion(containingObjectEl.fileLocations, IdColonTokenRegion,
+ el->colonToken);
+ FileLocations::addRegion(containingObjectEl.fileLocations, IdNameRegion,
+ combineLocations(el->statement));
+ QmlComponent &comp = current<QmlComponent>();
+ pathFromOwner = comp.addId(idVal, AddOption::KeepExisting, &idPtr);
+ QRegularExpression idRe(QRegularExpression::anchoredPattern(
+ QStringLiteral(uR"([[:lower:]][[:lower:][:upper:]0-9_]*)")));
+ auto m = idRe.matchView(iExp->name);
+ if (!m.hasMatch()) {
+ qmlFile.addError(std::move(
+ astParseErrors()
+ .warning(tr("id attributes should only be a lower case letter "
+ "followed by letters, numbers or underscore, not %1")
+ .arg(iExp->name))
+ .withPath(pathFromOwner)));
+ }
+ } else {
+ pathFromOwner =
+ current<QmlObject>().addBinding(bindingV, AddOption::KeepExisting, &bindingPtr);
+ Q_ASSERT_X(bindingPtr, className, "binding could not be retrieved");
+ qmlFile.addError(std::move(
+ astParseErrors()
+ .warning(tr("id attributes should only be a lower case letter "
+ "followed by letters, numbers or underscore, not %1 "
+ "%2, assuming they refer to a property")
+ .arg(script->code(), script->astRelocatableDump()))
+ .withPath(pathFromOwner)));
+ }
+ } else {
+ pathFromOwner =
+ current<QmlObject>().addBinding(bindingV, AddOption::KeepExisting, &bindingPtr);
+ QmlStackElement &containingObjectEl = currentEl<QmlObject>();
+ // remove the containingObjectEl.path prefix from pathFromOwner
+ Path pathFromContainingObject = pathFromOwner.mid(containingObjectEl.path.length());
+ auto bindingFileLocation =
+ FileLocations::ensure(containingObjectEl.fileLocations, pathFromContainingObject);
+ FileLocations::addRegion(bindingFileLocation, IdentifierRegion,
+ el->qualifiedId->identifierToken);
+ FileLocations::addRegion(bindingFileLocation, ColonTokenRegion, el->colonToken);
+
+ setBindingIdentifiers(pathFromOwner, el->qualifiedId, bindingPtr);
+
+ Q_ASSERT_X(bindingPtr, className, "binding could not be retrieved");
+ }
+ if (bindingPtr)
+ pushEl(pathFromOwner, *bindingPtr, el);
+ else if (idPtr)
+ pushEl(pathFromOwner, *idPtr, el);
+ else
+ Q_UNREACHABLE();
+ loadAnnotations(el);
+ // avoid duplicate colon location for id?
+ FileLocations::addRegion(nodeStack.last().fileLocations, ColonTokenRegion, el->colonToken);
+ return true;
+}
+
+void QQmlDomAstCreator::setScriptExpression (const std::shared_ptr<ScriptExpression>& value)
+{
+ if (m_enableScriptExpressions
+ && (scriptNodeStack.size() != 1 || currentScriptNodeEl().isList()))
+ Q_SCRIPTELEMENT_DISABLE();
+ if (m_enableScriptExpressions) {
+ FileLocations::Tree valueLoc = FileLocations::ensure(currentNodeEl().fileLocations,
+ Path().field(Fields::value));
+ value->setScriptElement(finalizeScriptExpression(currentScriptNodeEl().takeVariant(),
+ Path().field(Fields::scriptElement),
+ valueLoc));
+ removeCurrentScriptNode({});
+ }
+};
+
+void QQmlDomAstCreator::endVisit(AST::UiScriptBinding *)
+{
+ DomValue &lastEl = currentNode();
+ index_type idx = currentIndex();
+ if (lastEl.kind == DomType::Binding) {
+ Binding &b = std::get<Binding>(lastEl.value);
+
+ setScriptExpression(b.scriptExpressionValue());
+
+ QmlObject &containingObject = current<QmlObject>();
+ Binding *bPtr = valueFromMultimap(containingObject.m_bindings, b.name(), idx);
+ Q_ASSERT(bPtr);
+ *bPtr = b;
+ } else if (lastEl.kind == DomType::Id) {
+ Id &id = std::get<Id>(lastEl.value);
+
+ setScriptExpression(id.value);
+
+ QmlComponent &comp = current<QmlComponent>();
+ Id *idPtr = valueFromMultimap(comp.m_ids, id.name, idx);
+ *idPtr = id;
+ } else {
+ Q_UNREACHABLE();
+ }
+
+ // there should be no more uncollected script elements
+ if (m_enableScriptExpressions && !scriptNodeStack.empty()) {
+ Q_SCRIPTELEMENT_DISABLE();
+ }
+ removeCurrentNode({});
+}
+
+bool QQmlDomAstCreator::visit(AST::UiArrayBinding *el)
+{
+ QList<QmlObject> value;
+ Binding bindingV(toString(el->qualifiedId), value, BindingType::Normal);
+ Binding *bindingPtr;
+ Path bindingPathFromOwner =
+ current<QmlObject>().addBinding(bindingV, AddOption::KeepExisting, &bindingPtr);
+ if (bindingV.name() == u"id")
+ qmlFile.addError(std::move(
+ astParseErrors()
+ .error(tr("id attributes should have only simple strings as values"))
+ .withPath(bindingPathFromOwner)));
+
+ setBindingIdentifiers(bindingPathFromOwner, el->qualifiedId, bindingPtr);
+
+ pushEl(bindingPathFromOwner, *bindingPtr, el);
+ FileLocations::addRegion(currentNodeEl().fileLocations, ColonTokenRegion, el->colonToken);
+ loadAnnotations(el);
+ FileLocations::Tree arrayList =
+ createMap(currentNodeEl().fileLocations, Path::Field(Fields::value), nullptr);
+ FileLocations::addRegion(arrayList, LeftBracketRegion, el->lbracketToken);
+ FileLocations::addRegion(arrayList, RightBracketRegion, el->rbracketToken);
+ arrayBindingLevels.append(nodeStack.size());
+ return true;
+}
+
+void QQmlDomAstCreator::endVisit(AST::UiArrayBinding *)
+{
+ index_type idx = currentIndex();
+ Binding &b = std::get<Binding>(currentNode().value);
+ Binding *bPtr = valueFromMultimap(current<QmlObject>().m_bindings, b.name(), idx);
+ *bPtr = b;
+ arrayBindingLevels.removeLast();
+ removeCurrentNode(DomType::Binding);
+}
+
+bool QQmlDomAstCreator::visit(AST::ArgumentList *list)
+{
+ if (!m_enableScriptExpressions)
+ return false;
+
+ auto currentList = makeScriptList(list);
+
+ for (auto it = list; it; it = it->next) {
+ Node::accept(it->expression, this);
+ if (!m_enableScriptExpressions)
+ return false;
+
+ if (scriptNodeStack.empty() || scriptNodeStack.last().isList()) {
+ Q_SCRIPTELEMENT_DISABLE();
+ return false;
+ }
+ currentList.append(scriptNodeStack.last().takeVariant());
+ scriptNodeStack.removeLast();
+ }
+
+ pushScriptElement(currentList);
+
+ return false; // return false because we already iterated over the children using the custom
+ // iteration above
+}
+
+bool QQmlDomAstCreator::visit(AST::UiParameterList *)
+{
+ return false; // do not create script node for Ui stuff
+}
+
+bool QQmlDomAstCreator::visit(AST::PatternElementList *list)
+{
+ if (!m_enableScriptExpressions)
+ return false;
+
+ auto currentList = makeScriptList(list);
+
+ for (auto it = list; it; it = it->next) {
+ if (it->elision) {
+ Node::accept(it->elision, this);
+ if (scriptNodeStack.empty() || !scriptNodeStack.last().isList()) {
+ Q_SCRIPTELEMENT_DISABLE();
+ return false;
+ }
+ currentList.append(scriptNodeStack.last().takeList());
+ scriptNodeStack.removeLast();
+ }
+ if (it->element) {
+ Node::accept(it->element, this);
+ if (scriptNodeStack.empty() || scriptNodeStack.last().isList()) {
+ Q_SCRIPTELEMENT_DISABLE();
+ return false;
+ }
+ currentList.append(scriptNodeStack.last().takeVariant());
+ scriptNodeStack.removeLast();
+ }
+ }
+
+ pushScriptElement(currentList);
+
+ return false; // return false because we already iterated over the children using the custom
+ // iteration above
+}
+
+bool QQmlDomAstCreator::visit(AST::PatternPropertyList *list)
+{
+ if (!m_enableScriptExpressions)
+ return false;
+
+ auto currentList = makeScriptList(list);
+
+ for (auto it = list; it; it = it->next) {
+ if (it->property) {
+ Node::accept(it->property, this);
+ if (!m_enableScriptExpressions)
+ return false;
+ if (scriptNodeStack.empty() || scriptNodeStack.last().isList()) {
+ Q_SCRIPTELEMENT_DISABLE();
+ return false;
+ }
+ currentList.append(scriptNodeStack.last().takeVariant());
+ scriptNodeStack.removeLast();
+ }
+ }
+
+ pushScriptElement(currentList);
+
+ return false; // return false because we already iterated over the children using the custom
+ // iteration above
+}
+
+/*!
+ \internal
+ Implementing the logic of this method in \c QQmlDomAstCreator::visit(AST::UiQualifiedId *)
+ would create scriptelements at places where there are not needed. This is mainly because
+ UiQualifiedId's appears inside and outside of script parts.
+*/
+ScriptElementVariant QQmlDomAstCreator::scriptElementForQualifiedId(AST::UiQualifiedId *expression)
+{
+ auto id = std::make_shared<ScriptElements::IdentifierExpression>(
+ expression->firstSourceLocation(), expression->lastSourceLocation());
+ id->setName(expression->toString());
+
+ return ScriptElementVariant::fromElement(id);
+}
+
+bool QQmlDomAstCreator::visit(AST::UiQualifiedId *)
+{
+ if (!m_enableScriptExpressions)
+ return false;
+
+ return false;
+}
+
+bool QQmlDomAstCreator::visit(AST::UiEnumDeclaration *el)
+{
+ EnumDecl eDecl;
+ eDecl.setName(el->name.toString());
+ EnumDecl *ePtr;
+ Path enumPathFromOwner =
+ current<QmlComponent>().addEnumeration(eDecl, AddOption::KeepExisting, &ePtr);
+ pushEl(enumPathFromOwner, *ePtr, el);
+ FileLocations::addRegion(nodeStack.last().fileLocations, EnumKeywordRegion, el->enumToken);
+ FileLocations::addRegion(nodeStack.last().fileLocations, IdentifierRegion, el->identifierToken);
+ loadAnnotations(el);
+ return true;
+}
+
+void QQmlDomAstCreator::endVisit(AST::UiEnumDeclaration *)
+{
+ EnumDecl &e = std::get<EnumDecl>(currentNode().value);
+ EnumDecl *ePtr =
+ valueFromMultimap(current<QmlComponent>().m_enumerations, e.name(), currentIndex());
+ Q_ASSERT(ePtr);
+ *ePtr = e;
+ removeCurrentNode(DomType::EnumDecl);
+}
+
+bool QQmlDomAstCreator::visit(AST::UiEnumMemberList *el)
+{
+ EnumItem it(el->member.toString(), el->value);
+ EnumDecl &eDecl = std::get<EnumDecl>(currentNode().value);
+ Path itPathFromDecl = eDecl.addValue(it);
+ const auto map = createMap(DomType::EnumItem, itPathFromDecl, nullptr);
+ FileLocations::addRegion(map, MainRegion, combine(el->memberToken, el->valueToken));
+ if (el->memberToken.isValid())
+ FileLocations::addRegion(map, IdentifierRegion, el->memberToken);
+ if (el->valueToken.isValid())
+ FileLocations::addRegion(map, EnumValueRegion, el->valueToken);
+ return true;
+}
+
+void QQmlDomAstCreator::endVisit(AST::UiEnumMemberList *el)
+{
+ Node::accept(el->next, this); // put other enum members at the same level as this one...
+}
+
+bool QQmlDomAstCreator::visit(AST::UiInlineComponent *el)
+{
+ QStringList els = current<QmlComponent>().name().split(QLatin1Char('.'));
+ els.append(el->name.toString());
+ QString cName = els.join(QLatin1Char('.'));
+ QmlComponent *compPtr;
+ Path p = qmlFilePtr->addComponent(QmlComponent(cName), AddOption::KeepExisting, &compPtr);
+
+ if (m_enableScriptExpressions) {
+ auto inlineComponentType =
+ makeGenericScriptElement(el->identifierToken, DomType::ScriptType);
+
+ auto typeName = std::make_shared<ScriptElements::IdentifierExpression>(el->identifierToken);
+ typeName->setName(el->name);
+ inlineComponentType->insertChild(Fields::typeName,
+ ScriptElementVariant::fromElement(typeName));
+ compPtr->setNameIdentifiers(
+ finalizeScriptExpression(ScriptElementVariant::fromElement(inlineComponentType),
+ p.field(Fields::nameIdentifiers), rootMap));
+ }
+
+ pushEl(p, *compPtr, el);
+ FileLocations::addRegion(nodeStack.last().fileLocations, ComponentKeywordRegion,
+ el->componentToken);
+ FileLocations::addRegion(nodeStack.last().fileLocations, IdentifierRegion, el->identifierToken);
+ loadAnnotations(el);
+ return true;
+}
+
+void QQmlDomAstCreator::endVisit(AST::UiInlineComponent *)
+{
+ QmlComponent &component = std::get<QmlComponent>(currentNode().value);
+ QStringList nameEls = component.name().split(QChar::fromLatin1('.'));
+ QString key = nameEls.mid(1).join(QChar::fromLatin1('.'));
+ QmlComponent *cPtr = valueFromMultimap(qmlFilePtr->lazyMembers().m_components, key, currentIndex());
+ Q_ASSERT(cPtr);
+ *cPtr = component;
+ removeCurrentNode(DomType::QmlComponent);
+}
+
+bool QQmlDomAstCreator::visit(UiRequired *el)
+{
+ PropertyDefinition pDef;
+ pDef.name = el->name.toString();
+ pDef.isRequired = true;
+ PropertyDefinition *pDefPtr;
+ Path pathFromOwner =
+ current<QmlObject>().addPropertyDef(pDef, AddOption::KeepExisting, &pDefPtr);
+ createMap(DomType::PropertyDefinition, pathFromOwner, el);
+ return false;
+}
+
+bool QQmlDomAstCreator::visit(AST::UiAnnotation *el)
+{
+ QmlObject a;
+ a.setName(QStringLiteral(u"@") + toString(el->qualifiedTypeNameId));
+ // add annotation prototype?
+ DomValue &containingElement = currentNode();
+ Path pathFromOwner;
+ QmlObject *aPtr = nullptr;
+ switch (containingElement.kind) {
+ case DomType::QmlObject:
+ pathFromOwner = std::get<QmlObject>(containingElement.value).addAnnotation(a, &aPtr);
+ break;
+ case DomType::Binding:
+ pathFromOwner = std::get<Binding>(containingElement.value)
+ .addAnnotation(currentNodeEl().path, a, &aPtr);
+ break;
+ case DomType::Id:
+ pathFromOwner =
+ std::get<Id>(containingElement.value).addAnnotation(currentNodeEl().path, a, &aPtr);
+ break;
+ case DomType::PropertyDefinition:
+ pathFromOwner = std::get<PropertyDefinition>(containingElement.value)
+ .addAnnotation(currentNodeEl().path, a, &aPtr);
+ break;
+ case DomType::MethodInfo:
+ pathFromOwner = std::get<MethodInfo>(containingElement.value)
+ .addAnnotation(currentNodeEl().path, a, &aPtr);
+ break;
+ default:
+ qCWarning(domLog) << "Unexpected container object for annotation:"
+ << domTypeToString(containingElement.kind);
+ Q_UNREACHABLE();
+ }
+ pushEl(pathFromOwner, *aPtr, el);
+ return true;
+}
+
+void QQmlDomAstCreator::endVisit(AST::UiAnnotation *)
+{
+ DomValue &containingElement = currentNode(1);
+ Path pathFromOwner;
+ QmlObject &a = std::get<QmlObject>(currentNode().value);
+ switch (containingElement.kind) {
+ case DomType::QmlObject:
+ std::get<QmlObject>(containingElement.value).m_annotations[currentIndex()] = a;
+ break;
+ case DomType::Binding:
+ std::get<Binding>(containingElement.value).m_annotations[currentIndex()] = a;
+ break;
+ case DomType::Id:
+ std::get<Id>(containingElement.value).annotations[currentIndex()] = a;
+ break;
+ case DomType::PropertyDefinition:
+ std::get<PropertyDefinition>(containingElement.value).annotations[currentIndex()] = a;
+ break;
+ case DomType::MethodInfo:
+ std::get<MethodInfo>(containingElement.value).annotations[currentIndex()] = a;
+ break;
+ default:
+ Q_UNREACHABLE();
+ }
+ removeCurrentNode(DomType::QmlObject);
+}
+
+void QQmlDomAstCreator::throwRecursionDepthError()
+{
+ qmlFile.addError(astParseErrors().error(
+ tr("Maximum statement or expression depth exceeded in QmlDomAstCreator")));
+}
+
+bool QQmlDomAstCreator::visit(AST::StatementList *)
+{
+ if (!m_enableScriptExpressions)
+ return false;
+
+ return true;
+}
+
+void QQmlDomAstCreator::endVisit(AST::StatementList *list)
+{
+ if (!m_enableScriptExpressions)
+ return;
+
+ auto current = makeScriptList(list);
+
+ for (auto it = list; it; it = it->next) {
+ Q_SCRIPTELEMENT_EXIT_IF(scriptNodeStack.isEmpty() || scriptNodeStack.last().isList());
+ current.append(scriptNodeStack.takeLast().takeVariant());
+ }
+
+ current.reverse();
+ pushScriptElement(current);
+}
+
+bool QQmlDomAstCreator::visit(AST::BinaryExpression *)
+{
+ if (!m_enableScriptExpressions)
+ return false;
+
+ return true;
+}
+
+void QQmlDomAstCreator::endVisit(AST::BinaryExpression *exp)
+{
+ if (!m_enableScriptExpressions)
+ return;
+
+ auto current = makeScriptElement<ScriptElements::BinaryExpression>(exp);
+ current->addLocation(OperatorTokenRegion, exp->operatorToken);
+ Q_SCRIPTELEMENT_EXIT_IF(scriptNodeStack.isEmpty() || scriptNodeStack.last().isList());
+ current->setRight(currentScriptNodeEl().takeVariant());
+ removeCurrentScriptNode({});
+ Q_SCRIPTELEMENT_EXIT_IF(scriptNodeStack.isEmpty() || scriptNodeStack.last().isList());
+ current->setLeft(currentScriptNodeEl().takeVariant());
+ removeCurrentScriptNode({});
+
+ pushScriptElement(current);
+}
+
+bool QQmlDomAstCreator::visit(AST::Block *)
+{
+ if (!m_enableScriptExpressions)
+ return false;
+
+ return true;
+}
+
+void QQmlDomAstCreator::endVisit(AST::Block *block)
+{
+ if (!m_enableScriptExpressions)
+ return;
+
+ auto current = makeScriptElement<ScriptElements::BlockStatement>(block);
+
+ if (block->statements) {
+ Q_SCRIPTELEMENT_EXIT_IF(scriptNodeStack.isEmpty());
+ current->setStatements(currentScriptNodeEl().takeList());
+ removeCurrentScriptNode(DomType::List);
+ }
+
+ pushScriptElement(current);
+}
+
+bool QQmlDomAstCreator::visit(AST::ForStatement *)
+{
+ if (!m_enableScriptExpressions)
+ return false;
+
+ return true;
+}
+
+void QQmlDomAstCreator::endVisit(AST::ForStatement *forStatement)
+{
+ if (!m_enableScriptExpressions)
+ return;
+
+ auto current = makeScriptElement<ScriptElements::ForStatement>(forStatement);
+ current->addLocation(FileLocationRegion::ForKeywordRegion, forStatement->forToken);
+ current->addLocation(FileLocationRegion::LeftParenthesisRegion, forStatement->lparenToken);
+ current->addLocation(FileLocationRegion::FirstSemicolonTokenRegion,
+ forStatement->firstSemicolonToken);
+ current->addLocation(FileLocationRegion::SecondSemicolonRegion,
+ forStatement->secondSemicolonToken);
+ current->addLocation(FileLocationRegion::RightParenthesisRegion, forStatement->rparenToken);
+
+ if (forStatement->statement) {
+ Q_SCRIPTELEMENT_EXIT_IF(scriptNodeStack.isEmpty() || scriptNodeStack.last().isList());
+ current->setBody(currentScriptNodeEl().takeVariant());
+ removeCurrentScriptNode(std::nullopt);
+ }
+
+ if (forStatement->expression) {
+ Q_SCRIPTELEMENT_EXIT_IF(scriptNodeStack.isEmpty() || scriptNodeStack.last().isList());
+ current->setExpression(currentScriptNodeEl().takeVariant());
+ removeCurrentScriptNode(std::nullopt);
+ }
+
+ if (forStatement->condition) {
+ Q_SCRIPTELEMENT_EXIT_IF(scriptNodeStack.isEmpty() || scriptNodeStack.last().isList());
+ current->setCondition(currentScriptNodeEl().takeVariant());
+ removeCurrentScriptNode(std::nullopt);
+ }
+
+ if (forStatement->declarations) {
+ Q_SCRIPTELEMENT_EXIT_IF(scriptNodeStack.isEmpty());
+ auto variableDeclaration = makeGenericScriptElement(forStatement->declarations,
+ DomType::ScriptVariableDeclaration);
+
+ ScriptElements::ScriptList list = currentScriptNodeEl().takeList();
+ list.replaceKindForGenericChildren(DomType::ScriptPattern,
+ DomType::ScriptVariableDeclarationEntry);
+ variableDeclaration->insertChild(Fields::declarations, std::move(list));
+ removeCurrentScriptNode({});
+
+ current->setDeclarations(ScriptElementVariant::fromElement(variableDeclaration));
+ }
+
+ if (forStatement->initialiser) {
+ Q_SCRIPTELEMENT_EXIT_IF(scriptNodeStack.isEmpty() || scriptNodeStack.last().isList());
+ current->setInitializer(currentScriptNodeEl().takeVariant());
+ removeCurrentScriptNode(std::nullopt);
+ }
+ pushScriptElement(current);
+}
+
+bool QQmlDomAstCreator::visit(AST::IdentifierExpression *expression)
+{
+ if (!m_enableScriptExpressions)
+ return false;
+
+ auto current = makeScriptElement<ScriptElements::IdentifierExpression>(expression);
+ current->setName(expression->name);
+ pushScriptElement(current);
+ return true;
+}
+
+bool QQmlDomAstCreator::visit(AST::NumericLiteral *expression)
+{
+ if (!m_enableScriptExpressions)
+ return false;
+
+ auto current = makeScriptElement<ScriptElements::Literal>(expression);
+ current->setLiteralValue(expression->value);
+ pushScriptElement(current);
+ return true;
+}
+
+bool QQmlDomAstCreator::visit(AST::StringLiteral *expression)
+{
+ if (!m_enableScriptExpressions)
+ return false;
+
+ pushScriptElement(makeStringLiteral(expression->value, expression));
+ return true;
+}
+
+bool QQmlDomAstCreator::visit(AST::NullExpression *expression)
+{
+ if (!m_enableScriptExpressions)
+ return false;
+
+ auto current = makeScriptElement<ScriptElements::Literal>(expression);
+ current->setLiteralValue(nullptr);
+ pushScriptElement(current);
+ return true;
+}
+
+bool QQmlDomAstCreator::visit(AST::TrueLiteral *expression)
+{
+ if (!m_enableScriptExpressions)
+ return false;
+
+ auto current = makeScriptElement<ScriptElements::Literal>(expression);
+ current->setLiteralValue(true);
+ pushScriptElement(current);
+ return true;
+}
+
+bool QQmlDomAstCreator::visit(AST::FalseLiteral *expression)
+{
+ if (!m_enableScriptExpressions)
+ return false;
+
+ auto current = makeScriptElement<ScriptElements::Literal>(expression);
+ current->setLiteralValue(false);
+ pushScriptElement(current);
+ return true;
+}
+
+bool QQmlDomAstCreator::visit(AST::IdentifierPropertyName *expression)
+{
+ if (!m_enableScriptExpressions)
+ return false;
+
+ auto current = makeScriptElement<ScriptElements::IdentifierExpression>(expression);
+ current->setName(expression->id);
+ pushScriptElement(current);
+ return true;
+}
+
+bool QQmlDomAstCreator::visit(AST::StringLiteralPropertyName *expression)
+{
+ if (!m_enableScriptExpressions)
+ return false;
+
+ pushScriptElement(makeStringLiteral(expression->id, expression));
+ return true;
+}
+
+bool QQmlDomAstCreator::visit(AST::TypeAnnotation *)
+{
+ if (!m_enableScriptExpressions)
+ return false;
+
+ // do nothing: the work is done in (end)visit(AST::Type*).
+ return true;
+}
+
+bool QQmlDomAstCreator::visit(AST::NumericLiteralPropertyName *expression)
+{
+ if (!m_enableScriptExpressions)
+ return false;
+
+ auto current = makeScriptElement<ScriptElements::Literal>(expression);
+ current->setLiteralValue(expression->id);
+ pushScriptElement(current);
+ return true;
+}
+
+bool QQmlDomAstCreator::visit(AST::ComputedPropertyName *)
+{
+ if (!m_enableScriptExpressions)
+ return false;
+
+ // nothing to do, just forward the underlying expression without changing/wrapping it
+ return true;
+}
+
+bool QQmlDomAstCreator::visit(AST::VariableDeclarationList *list)
+{
+ if (!m_enableScriptExpressions)
+ return false;
+
+ auto currentList = makeScriptList(list);
+
+ for (auto it = list; it; it = it->next) {
+ if (it->declaration) {
+ Node::accept(it->declaration, this);
+ if (!m_enableScriptExpressions)
+ return false;
+ if (scriptNodeStack.empty() || scriptNodeStack.last().isList()) {
+ Q_SCRIPTELEMENT_DISABLE();
+ return false;
+ }
+ currentList.append(scriptNodeStack.last().takeVariant());
+ scriptNodeStack.removeLast();
+ }
+ }
+ pushScriptElement(currentList);
+
+ return false; // return false because we already iterated over the children using the custom
+ // iteration above
+}
+
+bool QQmlDomAstCreator::visit(AST::Elision *list)
+{
+ if (!m_enableScriptExpressions)
+ return false;
+
+ auto currentList = makeScriptList(list);
+
+ for (auto it = list; it; it = it->next) {
+ auto current = makeGenericScriptElement(it->commaToken, DomType::ScriptElision);
+ currentList.append(ScriptElementVariant::fromElement(current));
+ }
+ pushScriptElement(currentList);
+
+ return false; // return false because we already iterated over the children using the custom
+ // iteration above
+}
+
+bool QQmlDomAstCreator::visit(AST::PatternElement *)
+{
+ if (!m_enableScriptExpressions)
+ return false;
+
+ return true;
+}
+
+/*!
+ \internal
+ Avoid code-duplication, reuse this code when doing endVisit on types inheriting from
+ AST::PatternElement.
+*/
+void QQmlDomAstCreator::endVisitHelper(
+ AST::PatternElement *pe,
+ const std::shared_ptr<ScriptElements::GenericScriptElement> &current)
+{
+ if (pe->equalToken.isValid())
+ current->addLocation(FileLocationRegion::EqualTokenRegion, pe->equalToken);
+
+ if (pe->identifierToken.isValid() && !pe->bindingIdentifier.isEmpty()) {
+ auto identifier =
+ std::make_shared<ScriptElements::IdentifierExpression>(pe->identifierToken);
+ identifier->setName(pe->bindingIdentifier);
+ current->insertChild(Fields::identifier, ScriptElementVariant::fromElement(identifier));
+ }
+ if (pe->initializer) {
+ Q_SCRIPTELEMENT_EXIT_IF(scriptNodeStack.isEmpty() || scriptNodeStack.last().isList());
+ current->insertChild(Fields::initializer, scriptNodeStack.last().takeVariant());
+ scriptNodeStack.removeLast();
+ }
+ if (pe->typeAnnotation) {
+ Q_SCRIPTELEMENT_EXIT_IF(scriptNodeStack.isEmpty() || scriptNodeStack.last().isList());
+ current->insertChild(Fields::type, scriptNodeStack.last().takeVariant());
+ scriptNodeStack.removeLast();
+ }
+ if (pe->bindingTarget) {
+ Q_SCRIPTELEMENT_EXIT_IF(scriptNodeStack.isEmpty() || scriptNodeStack.last().isList());
+ current->insertChild(Fields::bindingElement, scriptNodeStack.last().takeVariant());
+ scriptNodeStack.removeLast();
+ }
+}
+
+void QQmlDomAstCreator::endVisit(AST::PatternElement *pe)
+{
+ if (!m_enableScriptExpressions)
+ return;
+
+ auto element = makeGenericScriptElement(pe, DomType::ScriptPattern);
+ endVisitHelper(pe, element);
+ // check if helper disabled scriptexpressions
+ if (!m_enableScriptExpressions)
+ return;
+
+ pushScriptElement(element);
+}
+
+bool QQmlDomAstCreator::visit(AST::IfStatement *)
+{
+ if (!m_enableScriptExpressions)
+ return false;
+
+ return true;
+}
+
+void QQmlDomAstCreator::endVisit(AST::IfStatement *ifStatement)
+{
+ if (!m_enableScriptExpressions)
+ return;
+
+ auto current = makeScriptElement<ScriptElements::IfStatement>(ifStatement);
+ current->addLocation(LeftParenthesisRegion, ifStatement->lparenToken);
+ current->addLocation(RightParenthesisRegion, ifStatement->rparenToken);
+ current->addLocation(ElseKeywordRegion, ifStatement->elseToken);
+
+ if (ifStatement->ko) {
+ Q_SCRIPTELEMENT_EXIT_IF(scriptNodeStack.isEmpty() || scriptNodeStack.last().isList());
+ current->setAlternative(scriptNodeStack.last().takeVariant());
+ scriptNodeStack.removeLast();
+ }
+
+ if (ifStatement->ok) {
+ Q_SCRIPTELEMENT_EXIT_IF(scriptNodeStack.isEmpty() || scriptNodeStack.last().isList());
+ current->setConsequence(scriptNodeStack.last().takeVariant());
+ scriptNodeStack.removeLast();
+ }
+ if (ifStatement->expression) {
+ Q_SCRIPTELEMENT_EXIT_IF(scriptNodeStack.isEmpty() || scriptNodeStack.last().isList());
+ current->setCondition(scriptNodeStack.last().takeVariant());
+ scriptNodeStack.removeLast();
+ }
+
+ pushScriptElement(current);
+}
+
+bool QQmlDomAstCreator::visit(AST::ReturnStatement *)
+{
+ if (!m_enableScriptExpressions)
+ return false;
+
+ return true;
+}
+
+void QQmlDomAstCreator::endVisit(AST::ReturnStatement *returnStatement)
+{
+ if (!m_enableScriptExpressions)
+ return;
+
+ auto current = makeScriptElement<ScriptElements::ReturnStatement>(returnStatement);
+ current->addLocation(ReturnKeywordRegion, returnStatement->returnToken);
+
+ if (returnStatement->expression) {
+ Q_SCRIPTELEMENT_EXIT_IF(scriptNodeStack.isEmpty() || scriptNodeStack.last().isList());
+ current->setExpression(currentScriptNodeEl().takeVariant());
+ removeCurrentScriptNode({});
+ }
+
+ pushScriptElement(current);
+}
+
+bool QQmlDomAstCreator::visit(AST::FieldMemberExpression *)
+{
+ if (!m_enableScriptExpressions)
+ return false;
+
+ return true;
+}
+
+void QQmlDomAstCreator::endVisit(AST::FieldMemberExpression *expression)
+{
+ if (!m_enableScriptExpressions)
+ return;
+
+ auto current = makeScriptElement<ScriptElements::BinaryExpression>(expression);
+ current->setOp(ScriptElements::BinaryExpression::FieldMemberAccess);
+ current->addLocation(FileLocationRegion::OperatorTokenRegion, expression->dotToken);
+
+ if (expression->base) {
+ Q_SCRIPTELEMENT_EXIT_IF(scriptNodeStack.isEmpty() || scriptNodeStack.last().isList());
+ current->setLeft(currentScriptNodeEl().takeVariant());
+ removeCurrentScriptNode({});
+ }
+
+ auto scriptIdentifier =
+ std::make_shared<ScriptElements::IdentifierExpression>(expression->identifierToken);
+ scriptIdentifier->setName(expression->name);
+ current->setRight(ScriptElementVariant::fromElement(scriptIdentifier));
+
+ pushScriptElement(current);
+}
+
+bool QQmlDomAstCreator::visit(AST::ArrayMemberExpression *)
+{
+ if (!m_enableScriptExpressions)
+ return false;
+
+ return true;
+}
+
+void QQmlDomAstCreator::endVisit(AST::ArrayMemberExpression *expression)
+{
+ if (!m_enableScriptExpressions)
+ return;
+
+ auto current = makeScriptElement<ScriptElements::BinaryExpression>(expression);
+ current->setOp(ScriptElements::BinaryExpression::ArrayMemberAccess);
+ current->addLocation(FileLocationRegion::OperatorTokenRegion, expression->lbracketToken);
+
+ if (expression->expression) {
+ Q_SCRIPTELEMENT_EXIT_IF(scriptNodeStack.isEmpty() || scriptNodeStack.last().isList());
+ // if scriptNodeStack.last() is fieldmember expression, add expression to it instead of
+ // creating new one
+ current->setRight(currentScriptNodeEl().takeVariant());
+ removeCurrentScriptNode({});
+ }
+
+ if (expression->base) {
+ Q_SCRIPTELEMENT_EXIT_IF(scriptNodeStack.isEmpty() || scriptNodeStack.last().isList());
+ current->setLeft(currentScriptNodeEl().takeVariant());
+ removeCurrentScriptNode({});
+ }
+
+ pushScriptElement(current);
+}
+
+bool QQmlDomAstCreator::visit(AST::CallExpression *)
+{
+ if (!m_enableScriptExpressions)
+ return false;
+
+ return true;
+}
+
+void QQmlDomAstCreator::endVisit(AST::CallExpression *exp)
+{
+ if (!m_enableScriptExpressions)
+ return;
+
+ auto current = makeGenericScriptElement(exp, DomType::ScriptCallExpression);
+ current->addLocation(LeftParenthesisRegion, exp->lparenToken);
+ current->addLocation(RightParenthesisRegion, exp->rparenToken);
+
+ if (exp->arguments) {
+ Q_SCRIPTELEMENT_EXIT_IF(scriptNodeStack.isEmpty());
+ current->insertChild(Fields::arguments, currentScriptNodeEl().takeList());
+ removeCurrentScriptNode({});
+ } else {
+ // insert empty list
+ current->insertChild(Fields::arguments,
+ ScriptElements::ScriptList(exp->lparenToken, exp->rparenToken));
+ }
+
+ if (exp->base) {
+ Q_SCRIPTELEMENT_EXIT_IF(scriptNodeStack.isEmpty() || scriptNodeStack.last().isList());
+ current->insertChild(Fields::callee, currentScriptNodeEl().takeVariant());
+ removeCurrentScriptNode({});
+ }
+
+ pushScriptElement(current);
+}
+
+bool QQmlDomAstCreator::visit(AST::ArrayPattern *)
+{
+ if (!m_enableScriptExpressions)
+ return false;
+
+ return true;
+}
+
+void QQmlDomAstCreator::endVisit(AST::ArrayPattern *exp)
+{
+ if (!m_enableScriptExpressions)
+ return;
+
+ auto current = makeGenericScriptElement(exp, DomType::ScriptArray);
+
+ if (exp->elements) {
+ Q_SCRIPTELEMENT_EXIT_IF(scriptNodeStack.isEmpty());
+ ScriptElements::ScriptList list = currentScriptNodeEl().takeList();
+ list.replaceKindForGenericChildren(DomType::ScriptPattern, DomType::ScriptArrayEntry);
+ current->insertChild(Fields::elements, std::move(list));
+
+ removeCurrentScriptNode({});
+ } else {
+ // insert empty list
+ current->insertChild(Fields::elements,
+ ScriptElements::ScriptList(exp->lbracketToken, exp->rbracketToken));
+ }
+
+ pushScriptElement(current);
+}
+
+bool QQmlDomAstCreator::visit(AST::ObjectPattern *)
+{
+ if (!m_enableScriptExpressions)
+ return false;
+
+ return true;
+}
+
+void QQmlDomAstCreator::endVisit(AST::ObjectPattern *exp)
+{
+ if (!m_enableScriptExpressions)
+ return;
+
+ auto current = makeGenericScriptElement(exp, DomType::ScriptObject);
+
+ if (exp->properties) {
+ Q_SCRIPTELEMENT_EXIT_IF(scriptNodeStack.isEmpty());
+ current->insertChild(Fields::properties, currentScriptNodeEl().takeList());
+ removeCurrentScriptNode({});
+ } else {
+ // insert empty list
+ current->insertChild(Fields::properties,
+ ScriptElements::ScriptList(exp->lbraceToken, exp->rbraceToken));
+ }
+
+ pushScriptElement(current);
+}
+
+bool QQmlDomAstCreator::visit(AST::PatternProperty *)
+{
+ if (!m_enableScriptExpressions)
+ return false;
+
+ return true;
+}
+
+void QQmlDomAstCreator::endVisit(AST::PatternProperty *exp)
+{
+ if (!m_enableScriptExpressions)
+ return;
+
+ auto current = makeGenericScriptElement(exp, DomType::ScriptProperty);
+
+ // handle the stuff from PatternProperty's base class PatternElement
+ endVisitHelper(static_cast<PatternElement *>(exp), current);
+
+ // check if helper disabled scriptexpressions
+ if (!m_enableScriptExpressions)
+ return;
+
+ if (exp->name) {
+ Q_SCRIPTELEMENT_EXIT_IF(scriptNodeStack.isEmpty() || scriptNodeStack.last().isList());
+ current->insertChild(Fields::name, currentScriptNodeEl().takeVariant());
+ removeCurrentScriptNode({});
+ }
+
+ pushScriptElement(current);
+}
+
+bool QQmlDomAstCreator::visit(AST::VariableStatement *)
+{
+ if (!m_enableScriptExpressions)
+ return false;
+
+ return true;
+}
+
+void QQmlDomAstCreator::endVisit(AST::VariableStatement *statement)
+{
+ if (!m_enableScriptExpressions)
+ return;
+
+ auto current = makeGenericScriptElement(statement, DomType::ScriptVariableDeclaration);
+
+ if (statement->declarations) {
+ Q_SCRIPTELEMENT_EXIT_IF(scriptNodeStack.isEmpty());
+
+ ScriptElements::ScriptList list = currentScriptNodeEl().takeList();
+ list.replaceKindForGenericChildren(DomType::ScriptPattern,
+ DomType::ScriptVariableDeclarationEntry);
+ current->insertChild(Fields::declarations, std::move(list));
+
+ removeCurrentScriptNode({});
+ }
+
+ pushScriptElement(current);
+}
+
+bool QQmlDomAstCreator::visit(AST::Type *)
+{
+ if (!m_enableScriptExpressions)
+ return false;
+
+ return true;
+}
+
+void QQmlDomAstCreator::endVisit(AST::Type *exp)
+{
+ if (!m_enableScriptExpressions)
+ return;
+
+ auto current = makeGenericScriptElement(exp, DomType::ScriptType);
+
+ if (exp->typeArgument) {
+ current->insertChild(Fields::typeArgumentName,
+ fieldMemberExpressionForQualifiedId(exp->typeArgument));
+ }
+
+ if (exp->typeId) {
+ current->insertChild(Fields::typeName, fieldMemberExpressionForQualifiedId(exp->typeId));
+ }
+
+ pushScriptElement(current);
+}
+
+bool QQmlDomAstCreator::visit(AST::DefaultClause *)
+{
+ if (!m_enableScriptExpressions)
+ return false;
+
+ return true;
+}
+
+void QQmlDomAstCreator::endVisit(AST::DefaultClause *exp)
+{
+ if (!m_enableScriptExpressions)
+ return;
+
+ auto current = makeGenericScriptElement(exp, DomType::ScriptDefaultClause);
+ current->addLocation(DefaultKeywordRegion, exp->defaultToken);
+ current->addLocation(ColonTokenRegion, exp->colonToken);
+
+ if (exp->statements) {
+ Q_SCRIPTELEMENT_EXIT_IF(scriptNodeStack.isEmpty());
+ current->insertChild(Fields::statements, currentScriptNodeEl().takeList());
+ removeCurrentScriptNode({});
+ }
+
+ pushScriptElement(current);
+}
+
+bool QQmlDomAstCreator::visit(AST::CaseClause *)
+{
+ if (!m_enableScriptExpressions)
+ return false;
+
+ return true;
+}
+
+void QQmlDomAstCreator::endVisit(AST::CaseClause *exp)
+{
+ if (!m_enableScriptExpressions)
+ return;
+
+ auto current = makeGenericScriptElement(exp, DomType::ScriptCaseClause);
+ current->addLocation(FileLocationRegion::CaseKeywordRegion, exp->caseToken);
+ current->addLocation(FileLocationRegion::ColonTokenRegion, exp->colonToken);
+
+ if (exp->statements) {
+ Q_SCRIPTELEMENT_EXIT_IF(scriptNodeStack.isEmpty());
+ current->insertChild(Fields::statements, currentScriptNodeEl().takeList());
+ removeCurrentScriptNode({});
+ }
+
+ if (exp->expression) {
+ Q_SCRIPTELEMENT_EXIT_IF(scriptNodeStack.isEmpty() || scriptNodeStack.last().isList());
+ current->insertChild(Fields::expression, currentScriptNodeEl().takeVariant());
+ removeCurrentScriptNode({});
+ }
+
+ pushScriptElement(current);
+}
+
+bool QQmlDomAstCreator::visit(AST::CaseClauses *)
+{
+ if (!m_enableScriptExpressions)
+ return false;
+
+ return true;
+}
+
+void QQmlDomAstCreator::endVisit(AST::CaseClauses *list)
+{
+ if (!m_enableScriptExpressions)
+ return;
+
+ auto current = makeScriptList(list);
+
+ for (auto it = list; it; it = it->next) {
+ Q_SCRIPTELEMENT_EXIT_IF(scriptNodeStack.isEmpty() || scriptNodeStack.last().isList());
+ current.append(scriptNodeStack.takeLast().takeVariant());
+ }
+
+ current.reverse();
+ pushScriptElement(current);
+}
+
+bool QQmlDomAstCreator::visit(AST::CaseBlock *)
+{
+ if (!m_enableScriptExpressions)
+ return false;
+
+ return true;
+}
+
+void QQmlDomAstCreator::endVisit(AST::CaseBlock *exp)
+{
+ if (!m_enableScriptExpressions)
+ return;
+
+ auto current = makeGenericScriptElement(exp, DomType::ScriptCaseBlock);
+ current->addLocation(FileLocationRegion::LeftBraceRegion, exp->lbraceToken);
+ current->addLocation(FileLocationRegion::RightBraceRegion, exp->rbraceToken);
+
+ if (exp->moreClauses) {
+ Q_SCRIPTELEMENT_EXIT_IF(scriptNodeStack.isEmpty());
+ current->insertChild(Fields::moreCaseClauses, currentScriptNodeEl().takeList());
+ removeCurrentScriptNode({});
+ }
+
+ if (exp->defaultClause) {
+ Q_SCRIPTELEMENT_EXIT_IF(scriptNodeStack.isEmpty() || scriptNodeStack.last().isList());
+ current->insertChild(Fields::defaultClause, currentScriptNodeEl().takeVariant());
+ removeCurrentScriptNode({});
+ }
+
+ if (exp->clauses) {
+ Q_SCRIPTELEMENT_EXIT_IF(scriptNodeStack.isEmpty());
+ current->insertChild(Fields::caseClauses, currentScriptNodeEl().takeList());
+ removeCurrentScriptNode({});
+ }
+ pushScriptElement(current);
+}
+
+bool QQmlDomAstCreator::visit(AST::SwitchStatement *)
+{
+ if (!m_enableScriptExpressions)
+ return false;
+
+ return true;
+}
+
+void QQmlDomAstCreator::endVisit(AST::SwitchStatement *exp)
+{
+ if (!m_enableScriptExpressions)
+ return;
+
+ auto current = makeGenericScriptElement(exp, DomType::ScriptSwitchStatement);
+ current->addLocation(FileLocationRegion::LeftParenthesisRegion, exp->lparenToken);
+ current->addLocation(FileLocationRegion::RightParenthesisRegion, exp->rparenToken);
+
+ if (exp->block) {
+ Q_SCRIPTELEMENT_EXIT_IF(scriptNodeStack.isEmpty() || scriptNodeStack.last().isList());
+ current->insertChild(Fields::caseBlock, currentScriptNodeEl().takeVariant());
+ removeCurrentScriptNode({});
+ }
+ if (exp->expression) {
+ Q_SCRIPTELEMENT_EXIT_IF(scriptNodeStack.isEmpty() || scriptNodeStack.last().isList());
+ current->insertChild(Fields::expression, currentScriptNodeEl().takeVariant());
+ removeCurrentScriptNode({});
+ }
+
+ pushScriptElement(current);
+}
+
+bool QQmlDomAstCreator::visit(AST::WhileStatement *)
+{
+ if (!m_enableScriptExpressions)
+ return false;
+
+ return true;
+}
+
+void QQmlDomAstCreator::endVisit(AST::WhileStatement *exp)
+{
+ if (!m_enableScriptExpressions)
+ return;
+
+ auto current = makeGenericScriptElement(exp, DomType::ScriptWhileStatement);
+ current->addLocation(FileLocationRegion::WhileKeywordRegion, exp->whileToken);
+ current->addLocation(FileLocationRegion::LeftParenthesisRegion, exp->lparenToken);
+ current->addLocation(FileLocationRegion::RightParenthesisRegion, exp->rparenToken);
+
+ if (exp->statement) {
+ Q_SCRIPTELEMENT_EXIT_IF(scriptNodeStack.isEmpty() || scriptNodeStack.last().isList());
+ current->insertChild(Fields::body, currentScriptNodeEl().takeVariant());
+ removeCurrentScriptNode({});
+ }
+
+ if (exp->expression) {
+ Q_SCRIPTELEMENT_EXIT_IF(scriptNodeStack.isEmpty() || scriptNodeStack.last().isList());
+ current->insertChild(Fields::expression, currentScriptNodeEl().takeVariant());
+ removeCurrentScriptNode({});
+ }
+
+ pushScriptElement(current);
+}
+
+bool QQmlDomAstCreator::visit(AST::DoWhileStatement *)
+{
+ if (!m_enableScriptExpressions)
+ return false;
+
+ return true;
+}
+
+void QQmlDomAstCreator::endVisit(AST::DoWhileStatement *exp)
+{
+ if (!m_enableScriptExpressions)
+ return;
+
+ auto current = makeGenericScriptElement(exp, DomType::ScriptDoWhileStatement);
+ current->addLocation(FileLocationRegion::DoKeywordRegion, exp->doToken);
+ current->addLocation(FileLocationRegion::WhileKeywordRegion, exp->whileToken);
+ current->addLocation(FileLocationRegion::LeftParenthesisRegion, exp->lparenToken);
+ current->addLocation(FileLocationRegion::RightParenthesisRegion, exp->rparenToken);
+
+ if (exp->expression) {
+ Q_SCRIPTELEMENT_EXIT_IF(scriptNodeStack.isEmpty() || scriptNodeStack.last().isList());
+ current->insertChild(Fields::expression, currentScriptNodeEl().takeVariant());
+ removeCurrentScriptNode({});
+ }
+
+ if (exp->statement) {
+ Q_SCRIPTELEMENT_EXIT_IF(scriptNodeStack.isEmpty() || scriptNodeStack.last().isList());
+ current->insertChild(Fields::body, currentScriptNodeEl().takeVariant());
+ removeCurrentScriptNode({});
+ }
+
+ pushScriptElement(current);
+}
+
+bool QQmlDomAstCreator::visit(AST::ForEachStatement *)
+{
+ if (!m_enableScriptExpressions)
+ return false;
+
+ return true;
+}
+
+void QQmlDomAstCreator::endVisit(AST::ForEachStatement *exp)
+{
+ if (!m_enableScriptExpressions)
+ return;
+
+ auto current = makeGenericScriptElement(exp, DomType::ScriptForEachStatement);
+ current->addLocation(FileLocationRegion::InOfTokenRegion, exp->inOfToken);
+ current->addLocation(FileLocationRegion::LeftParenthesisRegion, exp->lparenToken);
+ current->addLocation(FileLocationRegion::RightParenthesisRegion, exp->rparenToken);
+
+ if (exp->statement) {
+ Q_SCRIPTELEMENT_EXIT_IF(scriptNodeStack.isEmpty() || scriptNodeStack.last().isList());
+ current->insertChild(Fields::body, currentScriptNodeEl().takeVariant());
+ removeCurrentScriptNode({});
+ }
+ if (exp->expression) {
+ Q_SCRIPTELEMENT_EXIT_IF(scriptNodeStack.isEmpty() || scriptNodeStack.last().isList());
+ current->insertChild(Fields::expression, currentScriptNodeEl().takeVariant());
+ removeCurrentScriptNode({});
+ }
+
+ if (exp->lhs) {
+ Q_SCRIPTELEMENT_EXIT_IF(scriptNodeStack.isEmpty() || scriptNodeStack.last().isList());
+ current->insertChild(Fields::bindingElement, currentScriptNodeEl().takeVariant());
+ removeCurrentScriptNode({});
+ }
+
+ pushScriptElement(current);
+}
+
+
+bool QQmlDomAstCreator::visit(AST::ClassExpression *)
+{
+ // TODO: Add support for js expressions in classes
+ // For now, turning off explicitly to avoid unwanted problems
+ if (m_enableScriptExpressions)
+ Q_SCRIPTELEMENT_DISABLE();
+ return true;
+}
+
+void QQmlDomAstCreator::endVisit(AST::ClassExpression *)
+{
+}
+
+bool QQmlDomAstCreator::visit(AST::TemplateLiteral *)
+{
+ // TODO: Add support for template literals
+ // For now, turning off explicitly to avoid unwanted problems
+ if (m_enableScriptExpressions)
+ Q_SCRIPTELEMENT_DISABLE();
+ return true;
+}
+
+bool QQmlDomAstCreator::visit(AST::TryStatement *)
+{
+ return m_enableScriptExpressions;
+}
+
+void QQmlDomAstCreator::endVisit(AST::TryStatement *statement)
+{
+ if (!m_enableScriptExpressions)
+ return;
+
+ auto current = makeGenericScriptElement(statement, DomType::ScriptTryCatchStatement);
+ current->addLocation(FileLocationRegion::TryKeywordRegion, statement->tryToken);
+
+ if (auto exp = statement->finallyExpression) {
+ current->addLocation(FileLocationRegion::FinallyKeywordRegion, exp->finallyToken);
+
+ Q_SCRIPTELEMENT_EXIT_IF(scriptNodeStack.isEmpty() || scriptNodeStack.last().isList());
+ current->insertChild(Fields::finallyBlock, currentScriptNodeEl().takeVariant());
+ removeCurrentScriptNode({});
+ }
+
+ if (auto exp = statement->catchExpression) {
+ current->addLocation(FileLocationRegion::CatchKeywordRegion, exp->catchToken);
+ current->addLocation(FileLocationRegion::LeftParenthesisRegion, exp->lparenToken);
+ current->addLocation(FileLocationRegion::RightParenthesisRegion, exp->rparenToken);
+
+ Q_SCRIPTELEMENT_EXIT_IF(scriptNodeStack.isEmpty() || scriptNodeStack.last().isList());
+ current->insertChild(Fields::catchBlock, currentScriptNodeEl().takeVariant());
+ removeCurrentScriptNode({});
+ Q_SCRIPTELEMENT_EXIT_IF(scriptNodeStack.isEmpty() || scriptNodeStack.last().isList());
+ current->insertChild(Fields::catchParameter, currentScriptNodeEl().takeVariant());
+ removeCurrentScriptNode({});
+ }
+
+ if (statement->statement) {
+ Q_SCRIPTELEMENT_EXIT_IF(scriptNodeStack.isEmpty() || scriptNodeStack.last().isList());
+ current->insertChild(Fields::block, currentScriptNodeEl().takeVariant());
+ removeCurrentScriptNode({});
+ }
+
+ pushScriptElement(current);
+}
+
+bool QQmlDomAstCreator::visit(AST::Catch *)
+{
+ // handled in visit(AST::TryStatement* )
+ return m_enableScriptExpressions;
+}
+
+void QQmlDomAstCreator::endVisit(AST::Catch *)
+{
+ // handled in endVisit(AST::TryStatement* )
+}
+
+bool QQmlDomAstCreator::visit(AST::Finally *)
+{
+ // handled in visit(AST::TryStatement* )
+ return m_enableScriptExpressions;
+}
+
+void QQmlDomAstCreator::endVisit(AST::Finally *)
+{
+ // handled in endVisit(AST::TryStatement* )
+}
+
+bool QQmlDomAstCreator::visit(AST::ThrowStatement *)
+{
+ return m_enableScriptExpressions;
+}
+
+void QQmlDomAstCreator::endVisit(AST::ThrowStatement *statement)
+{
+ if (!m_enableScriptExpressions)
+ return;
+
+ auto current = makeGenericScriptElement(statement, DomType::ScriptThrowStatement);
+ current->addLocation(FileLocationRegion::ThrowKeywordRegion, statement->throwToken);
+
+ if (statement->expression) {
+ Q_SCRIPTELEMENT_EXIT_IF(scriptNodeStack.isEmpty() || scriptNodeStack.last().isList());
+ current->insertChild(Fields::expression, currentScriptNodeEl().takeVariant());
+ removeCurrentScriptNode({});
+ }
+
+ pushScriptElement(current);
+}
+
+bool QQmlDomAstCreator::visit(AST::LabelledStatement *)
+{
+ return m_enableScriptExpressions;
+}
+
+void QQmlDomAstCreator::endVisit(AST::LabelledStatement *statement)
+{
+ if (!m_enableScriptExpressions)
+ return;
+
+ auto current = makeGenericScriptElement(statement, DomType::ScriptLabelledStatement);
+ current->addLocation(FileLocationRegion::ColonTokenRegion, statement->colonToken);
+
+ auto label = std::make_shared<ScriptElements::IdentifierExpression>(statement->identifierToken);
+ label->setName(statement->label);
+ current->insertChild(Fields::label, ScriptElementVariant::fromElement(label));
+
+
+ if (statement->statement) {
+ Q_SCRIPTELEMENT_EXIT_IF(scriptNodeStack.isEmpty() || scriptNodeStack.last().isList());
+ current->insertChild(Fields::statement, currentScriptNodeEl().takeVariant());
+ removeCurrentScriptNode({});
+ }
+
+ pushScriptElement(current);
+}
+
+bool QQmlDomAstCreator::visit(AST::BreakStatement *)
+{
+ return m_enableScriptExpressions;
+}
+
+void QQmlDomAstCreator::endVisit(AST::BreakStatement *statement)
+{
+ if (!m_enableScriptExpressions)
+ return;
+
+ auto current = makeGenericScriptElement(statement, DomType::ScriptBreakStatement);
+ current->addLocation(FileLocationRegion::BreakKeywordRegion, statement->breakToken);
+
+ if (!statement->label.isEmpty()) {
+ auto label =
+ std::make_shared<ScriptElements::IdentifierExpression>(statement->identifierToken);
+ label->setName(statement->label);
+ current->insertChild(Fields::label, ScriptElementVariant::fromElement(label));
+ }
+
+ pushScriptElement(current);
+}
+
+// note: thats for comma expressions
+bool QQmlDomAstCreator::visit(AST::Expression *)
+{
+ return m_enableScriptExpressions;
+}
+
+// note: thats for comma expressions
+void QQmlDomAstCreator::endVisit(AST::Expression *commaExpression)
+{
+ if (!m_enableScriptExpressions)
+ return;
+
+ auto current = makeScriptElement<ScriptElements::BinaryExpression>(commaExpression);
+ current->addLocation(OperatorTokenRegion, commaExpression->commaToken);
+
+ if (commaExpression->right) {
+ Q_SCRIPTELEMENT_EXIT_IF(scriptNodeStack.isEmpty() || scriptNodeStack.last().isList());
+ current->setRight(currentScriptNodeEl().takeVariant());
+ removeCurrentScriptNode({});
+ }
+
+ if (commaExpression->left) {
+ Q_SCRIPTELEMENT_EXIT_IF(scriptNodeStack.isEmpty() || scriptNodeStack.last().isList());
+ current->setLeft(currentScriptNodeEl().takeVariant());
+ removeCurrentScriptNode({});
+ }
+
+ pushScriptElement(current);
+}
+
+bool QQmlDomAstCreator::visit(AST::ConditionalExpression *)
+{
+ return m_enableScriptExpressions;
+}
+
+void QQmlDomAstCreator::endVisit(AST::ConditionalExpression *expression)
+{
+ if (!m_enableScriptExpressions)
+ return;
+
+ auto current = makeGenericScriptElement(expression, DomType::ScriptConditionalExpression);
+ current->addLocation(FileLocationRegion::QuestionMarkTokenRegion, expression->questionToken);
+ current->addLocation(FileLocationRegion::ColonTokenRegion, expression->colonToken);
+
+ if (expression->ko) {
+ Q_SCRIPTELEMENT_EXIT_IF(scriptNodeStack.isEmpty() || scriptNodeStack.last().isList());
+ current->insertChild(Fields::alternative, currentScriptNodeEl().takeVariant());
+ removeCurrentScriptNode({});
+ }
+
+ if (expression->ok) {
+ Q_SCRIPTELEMENT_EXIT_IF(scriptNodeStack.isEmpty() || scriptNodeStack.last().isList());
+ current->insertChild(Fields::consequence, currentScriptNodeEl().takeVariant());
+ removeCurrentScriptNode({});
+ }
+
+ if (expression->expression) {
+ Q_SCRIPTELEMENT_EXIT_IF(scriptNodeStack.isEmpty() || scriptNodeStack.last().isList());
+ current->insertChild(Fields::condition, currentScriptNodeEl().takeVariant());
+ removeCurrentScriptNode({});
+ }
+
+ pushScriptElement(current);
+}
+
+bool QQmlDomAstCreator::visit(AST::ContinueStatement *)
+{
+ return m_enableScriptExpressions;
+}
+
+void QQmlDomAstCreator::endVisit(AST::ContinueStatement *statement)
+{
+ if (!m_enableScriptExpressions)
+ return;
+
+ auto current = makeGenericScriptElement(statement, DomType::ScriptContinueStatement);
+ current->addLocation(FileLocationRegion::ContinueKeywordRegion, statement->continueToken);
+
+ if (!statement->label.isEmpty()) {
+ auto label =
+ std::make_shared<ScriptElements::IdentifierExpression>(statement->identifierToken);
+ label->setName(statement->label);
+ current->insertChild(Fields::label, ScriptElementVariant::fromElement(label));
+ }
+
+ pushScriptElement(current);
+}
+
+/*!
+ \internal
+ Helper to create unary expressions from AST nodes.
+ \sa makeGenericScriptElement
+ */
+std::shared_ptr<ScriptElements::GenericScriptElement>
+QQmlDomAstCreator::makeUnaryExpression(AST::Node *expression, QQmlJS::SourceLocation operatorToken,
+ bool hasExpression, UnaryExpressionKind kind)
+{
+ const DomType type = [&kind]() {
+ switch (kind) {
+ case Prefix:
+ return DomType::ScriptUnaryExpression;
+ case Postfix:
+ return DomType::ScriptPostExpression;
+ }
+ Q_UNREACHABLE_RETURN(DomType::ScriptUnaryExpression);
+ }();
+
+ auto current = makeGenericScriptElement(expression, type);
+ current->addLocation(FileLocationRegion::OperatorTokenRegion, operatorToken);
+
+ if (hasExpression) {
+ if (scriptNodeStack.isEmpty() || scriptNodeStack.last().isList()) {
+ Q_SCRIPTELEMENT_DISABLE();
+ return {};
+ }
+ current->insertChild(Fields::expression, currentScriptNodeEl().takeVariant());
+ removeCurrentScriptNode({});
+ }
+
+ return current;
+}
+
+bool QQmlDomAstCreator::visit(AST::UnaryMinusExpression *)
+{
+ return m_enableScriptExpressions;
+}
+
+void QQmlDomAstCreator::endVisit(AST::UnaryMinusExpression *statement)
+{
+ if (!m_enableScriptExpressions)
+ return;
+
+ auto current =
+ makeUnaryExpression(statement, statement->minusToken, statement->expression, Prefix);
+ if (!current)
+ return;
+
+ pushScriptElement(current);
+}
+
+bool QQmlDomAstCreator::visit(AST::UnaryPlusExpression *)
+{
+ return m_enableScriptExpressions;
+}
+
+void QQmlDomAstCreator::endVisit(AST::UnaryPlusExpression *statement)
+{
+ if (!m_enableScriptExpressions)
+ return;
+
+ auto current =
+ makeUnaryExpression(statement, statement->plusToken, statement->expression, Prefix);
+ if (!current)
+ return;
+
+ pushScriptElement(current);
+}
+
+bool QQmlDomAstCreator::visit(AST::TildeExpression *)
+{
+ return m_enableScriptExpressions;
+}
+
+void QQmlDomAstCreator::endVisit(AST::TildeExpression *statement)
+{
+ if (!m_enableScriptExpressions)
+ return;
+
+ auto current =
+ makeUnaryExpression(statement, statement->tildeToken, statement->expression, Prefix);
+ if (!current)
+ return;
+
+ pushScriptElement(current);
+}
+
+bool QQmlDomAstCreator::visit(AST::NotExpression *)
+{
+ return m_enableScriptExpressions;
+}
+
+void QQmlDomAstCreator::endVisit(AST::NotExpression *statement)
+{
+ if (!m_enableScriptExpressions)
+ return;
+
+ auto current =
+ makeUnaryExpression(statement, statement->notToken, statement->expression, Prefix);
+ if (!current)
+ return;
+
+ pushScriptElement(current);
+}
+
+bool QQmlDomAstCreator::visit(AST::TypeOfExpression *)
+{
+ return m_enableScriptExpressions;
+}
+
+void QQmlDomAstCreator::endVisit(AST::TypeOfExpression *statement)
+{
+ if (!m_enableScriptExpressions)
+ return;
+
+ auto current =
+ makeUnaryExpression(statement, statement->typeofToken, statement->expression, Prefix);
+ if (!current)
+ return;
+
+ pushScriptElement(current);
+}
+
+bool QQmlDomAstCreator::visit(AST::DeleteExpression *)
+{
+ return m_enableScriptExpressions;
+}
+
+void QQmlDomAstCreator::endVisit(AST::DeleteExpression *statement)
+{
+ if (!m_enableScriptExpressions)
+ return;
+
+ auto current =
+ makeUnaryExpression(statement, statement->deleteToken, statement->expression, Prefix);
+ if (!current)
+ return;
+
+ pushScriptElement(current);
+}
+
+bool QQmlDomAstCreator::visit(AST::VoidExpression *)
+{
+ return m_enableScriptExpressions;
+}
+
+void QQmlDomAstCreator::endVisit(AST::VoidExpression *statement)
+{
+ if (!m_enableScriptExpressions)
+ return;
+
+ auto current =
+ makeUnaryExpression(statement, statement->voidToken, statement->expression, Prefix);
+ if (!current)
+ return;
+
+ pushScriptElement(current);
+}
+
+bool QQmlDomAstCreator::visit(AST::PostDecrementExpression *)
+{
+ return m_enableScriptExpressions;
+}
+
+void QQmlDomAstCreator::endVisit(AST::PostDecrementExpression *statement)
+{
+ if (!m_enableScriptExpressions)
+ return;
+
+ auto current =
+ makeUnaryExpression(statement, statement->decrementToken, statement->base, Postfix);
+ if (!current)
+ return;
+
+ pushScriptElement(current);
+}
+
+bool QQmlDomAstCreator::visit(AST::PostIncrementExpression *)
+{
+ return m_enableScriptExpressions;
+}
+
+void QQmlDomAstCreator::endVisit(AST::PostIncrementExpression *statement)
+{
+ if (!m_enableScriptExpressions)
+ return;
+
+ auto current =
+ makeUnaryExpression(statement, statement->incrementToken, statement->base, Postfix);
+ if (!current)
+ return;
+
+ pushScriptElement(current);
+}
+
+bool QQmlDomAstCreator::visit(AST::PreIncrementExpression *)
+{
+ return m_enableScriptExpressions;
+}
+
+void QQmlDomAstCreator::endVisit(AST::PreIncrementExpression *statement)
+{
+ if (!m_enableScriptExpressions)
+ return;
+
+ auto current = makeUnaryExpression(statement, statement->incrementToken, statement->expression,
+ Prefix);
+ if (!current)
+ return;
+
+ pushScriptElement(current);
+}
+
+bool QQmlDomAstCreator::visit(AST::EmptyStatement *)
+{
+ return m_enableScriptExpressions;
+}
+
+void QQmlDomAstCreator::endVisit(AST::EmptyStatement *statement)
+{
+ if (!m_enableScriptExpressions)
+ return;
+
+ auto current = makeGenericScriptElement(statement, DomType::ScriptEmptyStatement);
+ current->addLocation(FileLocationRegion::SemicolonTokenRegion, statement->semicolonToken);
+ pushScriptElement(current);
+}
+
+bool QQmlDomAstCreator::visit(AST::NestedExpression *)
+{
+ return m_enableScriptExpressions;
+}
+
+void QQmlDomAstCreator::endVisit(AST::NestedExpression *expression)
+{
+ if (!m_enableScriptExpressions)
+ return;
+
+ auto current = makeGenericScriptElement(expression, DomType::ScriptParenthesizedExpression);
+ current->addLocation(FileLocationRegion::LeftParenthesisRegion, expression->lparenToken);
+ current->addLocation(FileLocationRegion::RightParenthesisRegion, expression->rparenToken);
+
+ if (expression->expression) {
+ Q_SCRIPTELEMENT_EXIT_IF(scriptNodeStack.isEmpty() || scriptNodeStack.last().isList());
+ current->insertChild(Fields::expression, currentScriptNodeEl().takeVariant());
+ removeCurrentScriptNode({});
+ }
+
+ pushScriptElement(current);
+}
+
+bool QQmlDomAstCreator::visit(AST::PreDecrementExpression *)
+{
+ return m_enableScriptExpressions;
+}
+
+void QQmlDomAstCreator::endVisit(AST::PreDecrementExpression *statement)
+{
+ if (!m_enableScriptExpressions)
+ return;
+
+ auto current = makeUnaryExpression(statement, statement->decrementToken, statement->expression,
+ Prefix);
+ if (!current)
+ return;
+
+ pushScriptElement(current);
+}
+
+static const DomEnvironment *environmentFrom(MutableDomItem &qmlFile)
+{
+ auto top = qmlFile.top();
+ if (!top) {
+ return {};
+ }
+ auto domEnvironment = top.as<DomEnvironment>();
+ if (!domEnvironment) {
+ return {};
+ }
+ return domEnvironment;
+}
+
+static QStringList qmldirFilesFrom(MutableDomItem &qmlFile)
+{
+ if (auto env = environmentFrom(qmlFile))
+ return env->qmldirFiles();
+
+ return {};
+}
+
+QQmlDomAstCreatorWithQQmlJSScope::QQmlDomAstCreatorWithQQmlJSScope(const QQmlJSScope::Ptr &current,
+ MutableDomItem &qmlFile,
+ QQmlJSLogger *logger,
+ QQmlJSImporter *importer)
+ : m_root(current),
+ m_logger(logger),
+ m_importer(importer),
+ m_implicitImportDirectory(QQmlJSImportVisitor::implicitImportDirectory(
+ m_logger->fileName(), m_importer->resourceFileMapper())),
+ m_scopeCreator(m_root, m_importer, m_logger, m_implicitImportDirectory,
+ qmldirFilesFrom(qmlFile)),
+ m_domCreator(qmlFile)
+{
+}
+
+#define X(name) \
+ bool QQmlDomAstCreatorWithQQmlJSScope::visit(name *node) \
+ { \
+ return visitT(node); \
+ } \
+ void QQmlDomAstCreatorWithQQmlJSScope::endVisit(name *node) \
+ { \
+ endVisitT(node); \
+ }
+QQmlJSASTClassListToVisit
+#undef X
+
+void QQmlDomAstCreatorWithQQmlJSScope::setScopeInDomAfterEndvisit()
+{
+ const QQmlJSScope::ConstPtr scope = m_scopeCreator.m_currentScope;
+ if (!m_domCreator.scriptNodeStack.isEmpty()) {
+ auto topOfStack = m_domCreator.currentScriptNodeEl();
+ switch (topOfStack.kind) {
+ case DomType::ScriptBlockStatement:
+ case DomType::ScriptForStatement:
+ case DomType::ScriptForEachStatement:
+ case DomType::ScriptDoWhileStatement:
+ case DomType::ScriptWhileStatement:
+ case DomType::List:
+ m_domCreator.currentScriptNodeEl().setSemanticScope(scope);
+ break;
+ // TODO: find which script elements also have a scope and implement them here
+ default:
+ break;
+ };
+ } else if (!m_domCreator.nodeStack.isEmpty()) {
+ std::visit(
+ [&scope](auto &&e) {
+ using U = std::remove_cv_t<std::remove_reference_t<decltype(e)>>;
+ // TODO: find which dom elements also have a scope and implement them here
+ if constexpr (std::is_same_v<U, QmlObject>) {
+ e.setSemanticScope(scope);
+ } else if constexpr (std::is_same_v<U, QmlComponent>) {
+ e.setSemanticScope(scope);
+ } else if constexpr (std::is_same_v<U, MethodInfo>) {
+ if (e.body) {
+ if (auto scriptElement = e.body->scriptElement()) {
+ scriptElement.base()->setSemanticScope(scope);
+ }
+ }
+ e.setSemanticScope(scope);
+ }
+ },
+ m_domCreator.currentNodeEl().item.value);
+ }
+}
+
+void QQmlDomAstCreatorWithQQmlJSScope::setScopeInDomBeforeEndvisit()
+{
+ const QQmlJSScope::ConstPtr scope = m_scopeCreator.m_currentScope;
+
+ // depending whether the property definition has a binding, the property definition might be
+ // either at the last position in the stack or at the position before the last position.
+ if (m_domCreator.nodeStack.size() > 1
+ && m_domCreator.nodeStack.last().item.kind == DomType::Binding) {
+ std::visit(
+ [&scope](auto &&e) {
+ using U = std::remove_cv_t<std::remove_reference_t<decltype(e)>>;
+ if constexpr (std::is_same_v<U, PropertyDefinition>) {
+ // Make sure to use the property definition scope instead of the binding
+ // scope. If the current scope is a binding scope (this happens when the
+ // property definition has a binding, like `property int i: 45` for
+ // example), then the property definition scope is the parent of the current
+ // scope.
+ e.setSemanticScope(scope->scopeType() == QQmlSA::ScopeType::JSFunctionScope
+ ? scope->parentScope()
+ : scope);
+ Q_ASSERT(e.semanticScope()
+ && e.semanticScope()->scopeType() == QQmlSA::ScopeType::QMLScope);
+ }
+ },
+ m_domCreator.currentNodeEl(1).item.value);
+ }
+ if (m_domCreator.nodeStack.size() > 0) {
+ std::visit(
+ [&scope](auto &&e) {
+ using U = std::remove_cv_t<std::remove_reference_t<decltype(e)>>;
+ if constexpr (std::is_same_v<U, PropertyDefinition>) {
+ e.setSemanticScope(scope);
+ Q_ASSERT(e.semanticScope());
+ } else if constexpr (std::is_same_v<U, MethodInfo>) {
+ if (e.methodType == MethodInfo::Signal) {
+ e.setSemanticScope(scope);
+ }
+ }
+ },
+ m_domCreator.currentNodeEl().item.value);
+ }
+}
+
+void QQmlDomAstCreatorWithQQmlJSScope::throwRecursionDepthError()
+{
+}
+
+} // end namespace Dom
+} // end namespace QQmlJS
+
+#undef Q_SCRIPTELEMENT_DISABLE
+#undef Q_SCRIPTELEMENT_EXIT_IF
+
+QT_END_NAMESPACE
diff --git a/src/qmldom/qqmldomastcreator_p.h b/src/qmldom/qqmldomastcreator_p.h
new file mode 100644
index 0000000000..8176721d7e
--- /dev/null
+++ b/src/qmldom/qqmldomastcreator_p.h
@@ -0,0 +1,710 @@
+// Copyright (C) 2021 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+
+#ifndef QQMLDOMASTCREATOR_P_H
+#define QQMLDOMASTCREATOR_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 "qqmldomelements_p.h"
+#include "qqmldomitem_p.h"
+#include "qqmldompath_p.h"
+#include "qqmldomscriptelements_p.h"
+
+#include <QtQmlCompiler/private/qqmljsimportvisitor_p.h>
+
+#include <QtQml/private/qqmljsastvisitor_p.h>
+#include <memory>
+#include <type_traits>
+#include <variant>
+
+QT_BEGIN_NAMESPACE
+
+namespace QQmlJS {
+namespace Dom {
+
+class QQmlDomAstCreator final : public AST::Visitor
+{
+ Q_DECLARE_TR_FUNCTIONS(QQmlDomAstCreator)
+ using AST::Visitor::endVisit;
+ using AST::Visitor::visit;
+
+ static constexpr const auto className = "QmlDomAstCreator";
+
+ class DomValue
+ {
+ public:
+ template<typename T>
+ DomValue(const T &obj) : kind(T::kindValue), value(obj)
+ {
+ }
+ DomType kind;
+ std::variant<QmlObject, MethodInfo, QmlComponent, PropertyDefinition, Binding, EnumDecl,
+ EnumItem, ConstantData, Id>
+ value;
+ };
+
+ class QmlStackElement
+ {
+ public:
+ Path path;
+ DomValue item;
+ FileLocations::Tree fileLocations;
+ };
+
+ /*!
+ \internal
+ Contains a ScriptElementVariant, that can be used everywhere in the DOM representation, or a
+ List that should always be inside of something else, e.g., that cannot be the root of the
+ script element DOM representation.
+
+ Also, it makes sure you do not mistreat a list as a regular script element and vice versa.
+
+ The reason for this is that Lists can get pretty unintuitive, as a List could be a Block of
+ statements or a list of variable declarations (let i = 3, j = 4, ...) or something completely
+ different. Instead, always put lists inside named construct (BlockStatement,
+ VariableDeclaration, ...).
+ */
+ class ScriptStackElement
+ {
+ public:
+ template<typename T>
+ static ScriptStackElement from(const T &obj)
+ {
+ if constexpr (std::is_same_v<T, ScriptElements::ScriptList>) {
+ ScriptStackElement s{ ScriptElements::ScriptList::kindValue, obj };
+ return s;
+ } else {
+ ScriptStackElement s{ obj->kind(), ScriptElementVariant::fromElement(obj) };
+ return s;
+ }
+ Q_UNREACHABLE();
+ }
+
+ DomType kind;
+ using Variant = std::variant<ScriptElementVariant, ScriptElements::ScriptList>;
+ Variant value;
+
+ ScriptElementVariant takeVariant()
+ {
+ Q_ASSERT_X(std::holds_alternative<ScriptElementVariant>(value), "takeVariant",
+ "Should be a variant, did the parser change?");
+ return std::get<ScriptElementVariant>(std::move(value));
+ }
+
+ bool isList() const { return std::holds_alternative<ScriptElements::ScriptList>(value); };
+
+ ScriptElements::ScriptList takeList()
+ {
+ Q_ASSERT_X(std::holds_alternative<ScriptElements::ScriptList>(value), "takeList",
+ "Should be a List, did the parser change?");
+ return std::get<ScriptElements::ScriptList>(std::move(value));
+ }
+
+ void setSemanticScope(const QQmlJSScope::ConstPtr &scope)
+ {
+ if (auto x = std::get_if<ScriptElementVariant>(&value)) {
+ x->base()->setSemanticScope(scope);
+ return;
+ } else if (auto x = std::get_if<ScriptElements::ScriptList>(&value)) {
+ x->setSemanticScope(scope);
+ return;
+ }
+ Q_UNREACHABLE();
+ }
+ };
+
+public:
+ void enableScriptExpressions(bool enable = true) { m_enableScriptExpressions = enable; }
+ void enableLoadFileLazily(bool enable = true) { m_loadFileLazily = enable; }
+
+private:
+
+ MutableDomItem qmlFile;
+ std::shared_ptr<QmlFile> qmlFilePtr;
+ QVector<QmlStackElement> nodeStack;
+ QList<ScriptStackElement> scriptNodeStack;
+ QVector<int> arrayBindingLevels;
+ FileLocations::Tree rootMap;
+ bool m_enableScriptExpressions = false;
+ bool m_loadFileLazily = false;
+
+ // A Binding inside a UiPublicMember (= a Property definition) will shadow the
+ // propertydefinition's binding identifiers with its own binding identifiers. Therefore, disable
+ // bindingIdentifiers for the Binding inside a Property definition by using this flag.
+ bool m_skipBindingIdentifiers = false;
+
+ void setBindingIdentifiers(const Path &pathFromOwner, const AST::UiQualifiedId *identifiers,
+ Binding *bindingPtr);
+ template<typename T>
+ QmlStackElement &currentEl(int idx = 0)
+ {
+ Q_ASSERT_X(idx < nodeStack.size() && idx >= 0, "currentQmlObjectOrComponentEl",
+ "Stack does not contain enough elements!");
+ int i = nodeStack.size() - idx;
+ while (i-- > 0) {
+ DomType k = nodeStack.at(i).item.kind;
+ if (k == T::kindValue)
+ return nodeStack[i];
+ }
+ Q_ASSERT_X(false, "currentEl", "Stack does not contan object of type ");
+ return nodeStack.last();
+ }
+
+ template<typename T>
+ ScriptStackElement &currentScriptEl(int idx = 0)
+ {
+ Q_ASSERT_X(m_enableScriptExpressions, "currentScriptEl",
+ "Cannot access script elements when they are disabled!");
+
+ Q_ASSERT_X(idx < scriptNodeStack.size() && idx >= 0, "currentQmlObjectOrComponentEl",
+ "Stack does not contain enough elements!");
+ int i = scriptNodeStack.size() - idx;
+ while (i-- > 0) {
+ DomType k = scriptNodeStack.at(i).kind;
+ if (k == T::element_type::kindValue)
+ return scriptNodeStack[i];
+ }
+ Q_ASSERT_X(false, "currentEl", "Stack does not contain object of type ");
+ return scriptNodeStack.last();
+ }
+
+ template<typename T>
+ T &current(int idx = 0)
+ {
+ return std::get<T>(currentEl<T>(idx).item.value);
+ }
+
+ index_type currentIndex() { return currentNodeEl().path.last().headIndex(); }
+
+ QmlStackElement &currentQmlObjectOrComponentEl(int idx = 0);
+
+ QmlStackElement &currentNodeEl(int i = 0);
+ ScriptStackElement &currentScriptNodeEl(int i = 0);
+
+ DomValue &currentNode(int i = 0);
+
+ void removeCurrentNode(std::optional<DomType> expectedType);
+ void removeCurrentScriptNode(std::optional<DomType> expectedType);
+
+ void pushEl(const Path &p, const DomValue &it, AST::Node *n)
+ {
+ nodeStack.append({ p, it, createMap(it.kind, p, n) });
+ }
+
+ FileLocations::Tree createMap(const FileLocations::Tree &base, const Path &p, AST::Node *n);
+
+ FileLocations::Tree createMap(DomType k, const Path &p, AST::Node *n);
+
+ const ScriptElementVariant &
+ finalizeScriptExpression(const ScriptElementVariant &element, const Path &pathFromOwner,
+ const FileLocations::Tree &ownerFileLocations);
+
+ void setScriptExpression (const std::shared_ptr<ScriptExpression>& value);
+
+ Path pathOfLastScriptNode() const;
+
+ /*!
+ \internal
+ Helper to create string literals from AST nodes.
+ */
+ template<typename AstNodeT>
+ static std::shared_ptr<ScriptElements::Literal> makeStringLiteral(QStringView value,
+ AstNodeT *ast)
+ {
+ auto myExp = std::make_shared<ScriptElements::Literal>(ast->firstSourceLocation(),
+ ast->lastSourceLocation());
+ myExp->setLiteralValue(value.toString());
+ return myExp;
+ }
+
+ static std::shared_ptr<ScriptElements::Literal> makeStringLiteral(QStringView value,
+ QQmlJS::SourceLocation loc)
+ {
+ auto myExp = std::make_shared<ScriptElements::Literal>(loc);
+ myExp->setLiteralValue(value.toString());
+ return myExp;
+ }
+
+ /*!
+ \internal
+ Helper to create script elements from AST nodes, as the DOM classes should be completely
+ dependency-free from AST and parser classes. Using the AST classes in qqmldomastcreator is
+ fine because it needs them for the construction/visit. \sa makeScriptList
+ */
+ template<typename ScriptElementT, typename AstNodeT,
+ typename Enable =
+ std::enable_if_t<!std::is_same_v<ScriptElementT, ScriptElements::ScriptList>>>
+ static decltype(auto) makeScriptElement(AstNodeT *ast)
+ {
+ auto myExp = std::make_shared<ScriptElementT>(ast->firstSourceLocation(),
+ ast->lastSourceLocation());
+ return myExp;
+ }
+
+ /*!
+ \internal
+ Helper to create generic script elements from AST nodes.
+ \sa makeScriptElement
+ */
+ template<typename AstNodeT>
+ static std::shared_ptr<ScriptElements::GenericScriptElement>
+ makeGenericScriptElement(AstNodeT *ast, DomType kind)
+ {
+ auto myExp = std::make_shared<ScriptElements::GenericScriptElement>(
+ ast->firstSourceLocation(), ast->lastSourceLocation());
+ myExp->setKind(kind);
+ return myExp;
+ }
+
+ enum UnaryExpressionKind { Prefix, Postfix };
+ std::shared_ptr<ScriptElements::GenericScriptElement>
+ makeUnaryExpression(AST::Node *expression, QQmlJS::SourceLocation operatorToken,
+ bool hasExpression, UnaryExpressionKind type);
+
+ static std::shared_ptr<ScriptElements::GenericScriptElement>
+ makeGenericScriptElement(SourceLocation location, DomType kind)
+ {
+ auto myExp = std::make_shared<ScriptElements::GenericScriptElement>(location);
+ myExp->setKind(kind);
+ return myExp;
+ }
+
+ /*!
+ \internal
+ Helper to create script lists from AST nodes.
+ \sa makeScriptElement
+ */
+ template<typename AstNodeT>
+ static decltype(auto) makeScriptList(AstNodeT *ast)
+ {
+ auto myExp =
+ ScriptElements::ScriptList(ast->firstSourceLocation(), ast->lastSourceLocation());
+ return myExp;
+ }
+
+ template<typename ScriptElementT>
+ void pushScriptElement(const ScriptElementT &element)
+ {
+ Q_ASSERT_X(m_enableScriptExpressions, "pushScriptElement",
+ "Cannot create script elements when they are disabled!");
+ scriptNodeStack.append(ScriptStackElement::from(element));
+ }
+
+ void disableScriptElements()
+ {
+ m_enableScriptExpressions = false;
+ scriptNodeStack.clear();
+ }
+
+ ScriptElementVariant scriptElementForQualifiedId(AST::UiQualifiedId *expression);
+
+public:
+ explicit QQmlDomAstCreator(const MutableDomItem &qmlFile);
+
+ bool visit(AST::UiProgram *program) override;
+ void endVisit(AST::UiProgram *) override;
+
+ bool visit(AST::UiPragma *el) override;
+
+ bool visit(AST::UiImport *el) override;
+
+ bool visit(AST::UiPublicMember *el) override;
+ void endVisit(AST::UiPublicMember *el) override;
+
+ bool visit(AST::FunctionDeclaration *el) override;
+ void endVisit(AST::FunctionDeclaration *) override;
+
+ bool visit(AST::UiSourceElement *el) override;
+ void endVisit(AST::UiSourceElement *) override;
+
+ void loadAnnotations(AST::UiObjectMember *el) { AST::Node::accept(el->annotations, this); }
+
+ bool visit(AST::UiObjectDefinition *el) override;
+ void endVisit(AST::UiObjectDefinition *) override;
+
+ bool visit(AST::UiObjectBinding *el) override;
+ void endVisit(AST::UiObjectBinding *) override;
+
+ bool visit(AST::UiScriptBinding *el) override;
+ void endVisit(AST::UiScriptBinding *) override;
+
+ bool visit(AST::UiArrayBinding *el) override;
+ void endVisit(AST::UiArrayBinding *) override;
+
+ bool visit(AST::UiQualifiedId *) override;
+
+ bool visit(AST::UiEnumDeclaration *el) override;
+ void endVisit(AST::UiEnumDeclaration *) override;
+
+ bool visit(AST::UiEnumMemberList *el) override;
+ void endVisit(AST::UiEnumMemberList *el) override;
+
+ bool visit(AST::UiInlineComponent *el) override;
+ void endVisit(AST::UiInlineComponent *) override;
+
+ bool visit(AST::UiRequired *el) override;
+
+ bool visit(AST::UiAnnotation *el) override;
+ void endVisit(AST::UiAnnotation *) override;
+
+ // for Script elements:
+ bool visit(AST::BinaryExpression *exp) override;
+ void endVisit(AST::BinaryExpression *exp) override;
+
+ bool visit(AST::Block *block) override;
+ void endVisit(AST::Block *) override;
+
+ bool visit(AST::ReturnStatement *block) override;
+ void endVisit(AST::ReturnStatement *) override;
+
+ bool visit(AST::ForStatement *forStatement) override;
+ void endVisit(AST::ForStatement *forStatement) override;
+
+ bool visit(AST::PatternElement *pe) override;
+ void endVisit(AST::PatternElement *pe) override;
+ void endVisitHelper(AST::PatternElement *pe,
+ const std::shared_ptr<ScriptElements::GenericScriptElement> &element);
+
+ bool visit(AST::IfStatement *) override;
+ void endVisit(AST::IfStatement *) override;
+
+ bool visit(AST::FieldMemberExpression *) override;
+ void endVisit(AST::FieldMemberExpression *) override;
+
+ bool visit(AST::ArrayMemberExpression *) override;
+ void endVisit(AST::ArrayMemberExpression *) override;
+
+ bool visit(AST::CallExpression *) override;
+ void endVisit(AST::CallExpression *) override;
+
+ bool visit(AST::ArrayPattern *) override;
+ void endVisit(AST::ArrayPattern *) override;
+
+ bool visit(AST::ObjectPattern *) override;
+ void endVisit(AST::ObjectPattern *) override;
+
+ bool visit(AST::PatternProperty *) override;
+ void endVisit(AST::PatternProperty *) override;
+
+ bool visit(AST::VariableStatement *) override;
+ void endVisit(AST::VariableStatement *) override;
+
+ bool visit(AST::Type *expression) override;
+ void endVisit(AST::Type *expression) override;
+
+ bool visit(AST::DefaultClause *) override;
+ void endVisit(AST::DefaultClause *) override;
+
+ bool visit(AST::CaseClause *) override;
+ void endVisit(AST::CaseClause *) override;
+
+ bool visit(AST::CaseClauses *) override;
+ void endVisit(AST::CaseClauses *) override;
+
+ bool visit(AST::CaseBlock *) override;
+ void endVisit(AST::CaseBlock *) override;
+
+ bool visit(AST::SwitchStatement *) override;
+ void endVisit(AST::SwitchStatement *) override;
+
+ bool visit(AST::WhileStatement *) override;
+ void endVisit(AST::WhileStatement *) override;
+
+ bool visit(AST::DoWhileStatement *) override;
+ void endVisit(AST::DoWhileStatement *) override;
+
+ bool visit(AST::ForEachStatement *) override;
+ void endVisit(AST::ForEachStatement *) override;
+
+ bool visit(AST::ClassExpression *) override;
+ void endVisit(AST::ClassExpression *) override;
+
+ bool visit(AST::TemplateLiteral *) override;
+
+ bool visit(AST::TryStatement *) override;
+ void endVisit(AST::TryStatement *) override;
+
+ bool visit(AST::Catch *) override;
+ void endVisit(AST::Catch *) override;
+
+ bool visit(AST::Finally *) override;
+ void endVisit(AST::Finally *) override;
+
+ bool visit(AST::ThrowStatement *) override;
+ void endVisit(AST::ThrowStatement *) override;
+
+ bool visit(AST::LabelledStatement *) override;
+ void endVisit(AST::LabelledStatement *) override;
+
+ bool visit(AST::ContinueStatement *) override;
+ void endVisit(AST::ContinueStatement *) override;
+
+ bool visit(AST::BreakStatement *) override;
+ void endVisit(AST::BreakStatement *) override;
+
+ bool visit(AST::Expression *) override;
+ void endVisit(AST::Expression *) override;
+
+ bool visit(AST::ConditionalExpression *) override;
+ void endVisit(AST::ConditionalExpression *) override;
+
+ bool visit(AST::UnaryMinusExpression *) override;
+ void endVisit(AST::UnaryMinusExpression *) override;
+
+ bool visit(AST::UnaryPlusExpression *) override;
+ void endVisit(AST::UnaryPlusExpression *) override;
+
+ bool visit(AST::TildeExpression *) override;
+ void endVisit(AST::TildeExpression *) override;
+
+ bool visit(AST::NotExpression *) override;
+ void endVisit(AST::NotExpression *) override;
+
+ bool visit(AST::TypeOfExpression *) override;
+ void endVisit(AST::TypeOfExpression *) override;
+
+ bool visit(AST::DeleteExpression *) override;
+ void endVisit(AST::DeleteExpression *) override;
+
+ bool visit(AST::VoidExpression *) override;
+ void endVisit(AST::VoidExpression *) override;
+
+ bool visit(AST::PostDecrementExpression *) override;
+ void endVisit(AST::PostDecrementExpression *) override;
+
+ bool visit(AST::PostIncrementExpression *) override;
+ void endVisit(AST::PostIncrementExpression *) override;
+
+ bool visit(AST::PreDecrementExpression *) override;
+ void endVisit(AST::PreDecrementExpression *) override;
+
+ bool visit(AST::PreIncrementExpression *) override;
+ void endVisit(AST::PreIncrementExpression *) override;
+
+ bool visit(AST::EmptyStatement *) override;
+ void endVisit(AST::EmptyStatement *) override;
+
+ bool visit(AST::NestedExpression *) override;
+ void endVisit(AST::NestedExpression *) override;
+
+ // lists of stuff whose children do not need a qqmljsscope: visitation order can be custom
+ bool visit(AST::ArgumentList *) override;
+ bool visit(AST::UiParameterList *) override;
+ bool visit(AST::PatternElementList *) override;
+ bool visit(AST::PatternPropertyList *) override;
+ bool visit(AST::VariableDeclarationList *vdl) override;
+ bool visit(AST::Elision *elision) override;
+
+ // lists of stuff whose children need a qqmljsscope: visitation order cannot be custom
+ bool visit(AST::StatementList *list) override;
+ void endVisit(AST::StatementList *list) override;
+
+ // literals and ids
+ bool visit(AST::IdentifierExpression *expression) override;
+ bool visit(AST::NumericLiteral *expression) override;
+ bool visit(AST::StringLiteral *expression) override;
+ bool visit(AST::NullExpression *expression) override;
+ bool visit(AST::TrueLiteral *expression) override;
+ bool visit(AST::FalseLiteral *expression) override;
+ bool visit(AST::ComputedPropertyName *expression) override;
+ bool visit(AST::IdentifierPropertyName *expression) override;
+ bool visit(AST::NumericLiteralPropertyName *expression) override;
+ bool visit(AST::StringLiteralPropertyName *expression) override;
+ bool visit(AST::TypeAnnotation *expression) override;
+
+ void throwRecursionDepthError() override;
+
+public:
+ friend class QQmlDomAstCreatorWithQQmlJSScope;
+};
+
+class QQmlDomAstCreatorWithQQmlJSScope : public AST::Visitor
+{
+public:
+ QQmlDomAstCreatorWithQQmlJSScope(const QQmlJSScope::Ptr &current, MutableDomItem &qmlFile,
+ QQmlJSLogger *logger, QQmlJSImporter *importer);
+
+#define X(name) \
+ bool visit(AST::name *) override; \
+ void endVisit(AST::name *) override;
+ QQmlJSASTClassListToVisit
+#undef X
+
+ virtual void throwRecursionDepthError() override;
+ /*!
+ \internal
+ Disable the DOM for scriptexpressions, as not yet unimplemented script elements might crash
+ the construction.
+ */
+ void enableScriptExpressions(bool enable = true)
+ {
+ m_enableScriptExpressions = enable;
+ m_domCreator.enableScriptExpressions(enable);
+ }
+
+ void enableLoadFileLazily(bool enable = true)
+ {
+ m_loadFileLazily = enable;
+ m_domCreator.enableLoadFileLazily(enable);
+ }
+
+ QQmlJSImportVisitor &scopeCreator() { return m_scopeCreator; }
+
+private:
+ void setScopeInDomAfterEndvisit();
+ void setScopeInDomBeforeEndvisit();
+
+ template<typename U, typename... V>
+ using IsInList = std::disjunction<std::is_same<U, V>...>;
+ template<typename U>
+ using RequiresCustomIteration = IsInList<U, AST::PatternElementList, AST::PatternPropertyList,
+ AST::FormalParameterList>;
+
+ enum VisitorKind : bool { DomCreator, ScopeCreator };
+ /*! \internal
+ \brief Holds the information to reactivate a visitor
+ This struct tracks a visitor during its inactive phases
+ and holds the information needed to reactivate the visitor.
+ */
+ struct InactiveVisitorMarker
+ {
+ qsizetype count;
+ AST::Node::Kind nodeKind;
+ VisitorKind inactiveVisitorKind;
+
+ VisitorKind stillActiveVisitorKind() const
+ {
+ return inactiveVisitorKind == DomCreator ? ScopeCreator : DomCreator;
+ }
+ };
+
+ template<typename T>
+ void customListIteration(T *t)
+ {
+ static_assert(RequiresCustomIteration<T>::value);
+ for (auto it = t; it; it = it->next) {
+ if constexpr (std::is_same_v<T, AST::PatternElementList>) {
+ AST::Node::accept(it->elision, this);
+ AST::Node::accept(it->element, this);
+ } else if constexpr (std::is_same_v<T, AST::PatternPropertyList>) {
+ AST::Node::accept(it->property, this);
+ } else if constexpr (std::is_same_v<T, AST::FormalParameterList>) {
+ AST::Node::accept(it->element, this);
+ } else {
+ Q_UNREACHABLE();
+ }
+ }
+ }
+
+ static void initMarkerForActiveVisitor(std::optional<InactiveVisitorMarker> &inactiveVisitorMarker,
+ AST::Node::Kind nodeKind, bool continueForDom)
+ {
+ inactiveVisitorMarker.emplace();
+ inactiveVisitorMarker->inactiveVisitorKind = continueForDom ? ScopeCreator : DomCreator;
+ inactiveVisitorMarker->count = 1;
+ inactiveVisitorMarker->nodeKind = nodeKind;
+ };
+
+ template<typename T>
+ bool performListIterationIfRequired(T *t)
+ {
+ if constexpr (RequiresCustomIteration<T>::value) {
+ customListIteration(t);
+ return false;
+ }
+ Q_UNUSED(t);
+ return true;
+ }
+
+ template<typename T>
+ bool visitT(T *t)
+ {
+ const auto handleVisitResult = [this, t](const bool continueVisit) {
+ if (m_inactiveVisitorMarker && m_inactiveVisitorMarker->nodeKind == t->kind)
+ m_inactiveVisitorMarker->count += 1;
+
+ if (continueVisit)
+ return performListIterationIfRequired(t);
+ return continueVisit;
+ };
+
+ // first case: no marker, both can visit
+ if (!m_inactiveVisitorMarker) {
+ bool continueForDom = m_domCreator.visit(t);
+ bool continueForScope = m_scopeCreator.visit(t);
+ if (!continueForDom && !continueForScope)
+ return false;
+ else if (continueForDom ^ continueForScope) {
+ initMarkerForActiveVisitor(m_inactiveVisitorMarker, AST::Node::Kind(t->kind),
+ continueForDom);
+ return performListIterationIfRequired(t);
+ } else {
+ Q_ASSERT(continueForDom && continueForScope);
+ return performListIterationIfRequired(t);
+ }
+ Q_UNREACHABLE();
+ }
+
+ // second case: a marker, just one visit
+ switch (m_inactiveVisitorMarker->stillActiveVisitorKind()) {
+ case DomCreator:
+ return handleVisitResult(m_domCreator.visit(t));
+ case ScopeCreator:
+ return handleVisitResult(m_scopeCreator.visit(t));
+ };
+ Q_UNREACHABLE();
+ }
+
+ template<typename T>
+ void endVisitT(T *t)
+ {
+ if (m_inactiveVisitorMarker && m_inactiveVisitorMarker->nodeKind == t->kind) {
+ m_inactiveVisitorMarker->count -= 1;
+ if (m_inactiveVisitorMarker->count == 0)
+ m_inactiveVisitorMarker.reset();
+ }
+ if (m_inactiveVisitorMarker) {
+ switch (m_inactiveVisitorMarker->stillActiveVisitorKind()) {
+ case DomCreator:
+ m_domCreator.endVisit(t);
+ return;
+ case ScopeCreator:
+ m_scopeCreator.endVisit(t);
+ return;
+ };
+ Q_UNREACHABLE();
+ }
+
+ setScopeInDomBeforeEndvisit();
+ m_domCreator.endVisit(t);
+ setScopeInDomAfterEndvisit();
+ m_scopeCreator.endVisit(t);
+ }
+
+ QQmlJSScope::Ptr m_root;
+ QQmlJSLogger *m_logger = nullptr;
+ QQmlJSImporter *m_importer = nullptr;
+ QString m_implicitImportDirectory;
+ QQmlJSImportVisitor m_scopeCreator;
+ QQmlDomAstCreator m_domCreator;
+
+ std::optional<InactiveVisitorMarker> m_inactiveVisitorMarker;
+ bool m_enableScriptExpressions = false;
+ bool m_loadFileLazily = false;
+};
+
+} // end namespace Dom
+} // end namespace QQmlJS
+
+QT_END_NAMESPACE
+#endif // QQMLDOMASTCREATOR_P_H
diff --git a/src/qmldom/qqmldomastdumper.cpp b/src/qmldom/qqmldomastdumper.cpp
new file mode 100644
index 0000000000..b4986f7a71
--- /dev/null
+++ b/src/qmldom/qqmldomastdumper.cpp
@@ -0,0 +1,1106 @@
+// Copyright (C) 2020 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+
+#include "qqmldomastdumper_p.h"
+#include "qqmldomerrormessage_p.h"
+#include <QtQml/private/qqmljsast_p.h>
+#include <QtCore/QDebug>
+#include <QtCore/QString>
+#include <QtCore/QTextStream>
+
+QT_BEGIN_NAMESPACE
+
+namespace QQmlJS {
+namespace Dom {
+using namespace AST;
+/*!
+\internal
+\enum QQmlJS::AstDumperOption
+
+This enum type specifies the options for the AstDumper.
+The values can be combined with the '|' operator, and checked using the '&' operator.
+
+\value None
+ Default dumping options
+\value NoLocations
+ Does not dump SourceLocations, allowing one to compare equivalent AST
+ generated by code formatted differently
+\value NoAnnotations
+ Does not dump annotations
+\value DumpNode
+ Does dump a <Node></Node> in preVisit/postVisit
+*/
+
+/*!
+\internal
+\class QQmlJS::AstDumper
+\brief Dumps or compares AST in an xml like format, mostly for testing/debugging
+
+Initialize it with a lambda that dumps a string, and configure it with .setX methods.
+If \l{indent} is set to a non zero value the xml is indented by that amount, and
+\l{baseIndent} is the initial indent.
+If \l{emitNode} is true the node tag is emitted in the preVisit/postVisit.
+If \l{emitLocation} is true the SourceLocations are emitted.
+If \l{emitAnnotations} is true annotations are emitted
+
+The implementation has unnecessary roundtrips to QString, but it is supposed to be used
+for debugging purposes...
+
+Probably you will not use the visitor at all but rather the static method diff or
+the qDebug() and ostream operator << that use the visitor...
+
+\fn AstDumper::diff(AST::Node *n1, AST::Node *n2, int nContext, AstDumperOptions opt, int indent)
+
+\brief compares the two AST::Node n1 and n2 and returns a string describing their first difference
+
+If there are no differences the empty string is returned, so .isEmpty() can be use to check
+for no differences.
+\l{nContext} decides how much context is printed out.
+
+*/
+
+// no export, just a supporting class...
+class AstDumper: public AST::BaseVisitor
+{
+public:
+ AstDumper(const std::function<void(QStringView)> &dumper,
+ AstDumperOptions options = AstDumperOption::None, int indent = 1, int baseIndent = 0,
+ function_ref<QStringView(SourceLocation)> loc2str = &noStr)
+ : dumper(dumper), options(options), indent(indent), baseIndent(baseIndent), loc2str(loc2str)
+ {
+ }
+
+private:
+ void start(QStringView str) {
+ dumper(QString::fromLatin1(" ").repeated(baseIndent));
+ dumper(u"<");
+ dumper(str);
+ dumper(u">\n");
+ baseIndent += indent;
+ }
+
+ void stop(QStringView str) {
+ baseIndent -= indent;
+ dumper(QString::fromLatin1(" ").repeated(baseIndent));
+ dumper(u"</");
+ dumper(str);
+ dumper(u">\n");
+ }
+
+ QString quotedString(const QString &s) {
+ QString res(s);
+ return QStringLiteral(u"\"") + res
+ .replace(QLatin1String("\\"), QLatin1String("\\\\"))
+ .replace(QLatin1String("\""), QLatin1String("\\\"")) + QLatin1String("\"");
+ }
+
+ QString quotedString(QStringView s) {
+ return quotedString(s.toString());
+ }
+
+ QString loc(const SourceLocation &s, bool trim = false) {
+ QString tokenStr;
+ if (s.length > 0)
+ tokenStr = loc2str(s).toString()
+ .replace(QLatin1String("\\"), QLatin1String("\\\\"))
+ .replace(QLatin1String("\""),QLatin1String("\\\""));
+ if (trim)
+ tokenStr = tokenStr.trimmed();
+ if (noLocations() || s == SourceLocation())
+ return QLatin1String("\"%1\"").arg(tokenStr);
+ else {
+ return QLatin1String("\"off:%1 len:%2 l:%3 c:%4 %5\"").arg(QString::number(s.offset), QString::number(s.length), QString::number(s.startLine), QString::number(s.startColumn), tokenStr);
+ }
+ }
+
+ QString semicolonToken(const SourceLocation &s)
+ {
+ if (options & AstDumperOption::SloppyCompare)
+ return QString();
+ return QLatin1String(" semicolonToken=") + loc(s);
+ }
+
+ QString boolStr(bool v) { return (v ? quotedString(u"true"): quotedString(u"false")); }
+public:
+ bool preVisit(Node *) override { if (dumpNode()) start(u"Node"); return true; }
+ void postVisit(Node *) override { if (dumpNode()) stop(u"Node"); }
+
+ // Ui
+ bool visit(UiProgram *) override { start(u"UiProgram"); return true; }
+ void endVisit(AST::UiProgram *) override { stop(u"UiProgram"); }
+
+ bool visit(UiHeaderItemList *) override { start(u"UiHeaderItemList"); return true; }
+ void endVisit(AST::UiHeaderItemList *) override { stop(u"UiHeaderItemList"); }
+
+#if QT_VERSION >= QT_VERSION_CHECK(6, 6, 0)
+ bool visit(UiPragmaValueList *el) override {
+ start(QLatin1String("UiPragmaValueList value=%1").arg(el->value));
+ return true;
+ }
+ void endVisit(AST::UiPragmaValueList *) override { stop(u"UiPragmaValueList"); }
+#endif
+
+ bool visit(UiPragma *el) override {
+ start(QLatin1String("UiPragma name=%1 pragmaToken=%2%3")
+ .arg(quotedString(el->name), loc(el->pragmaToken),
+ semicolonToken(el->semicolonToken)));
+ return true;
+ }
+ void endVisit(AST::UiPragma *) override { stop(u"UiPragma"); }
+
+ bool visit(UiImport *el) override {
+ start(QLatin1String("UiImport fileName=%1 importId=%2 importToken=%3 fileNameToken=%4 "
+ "asToken=%5 importIdToken=%6%7")
+ .arg(quotedString(el->fileName), quotedString(el->importId),
+ loc(el->importToken), loc(el->fileNameToken), loc(el->asToken),
+ loc(el->importIdToken), semicolonToken(el->semicolonToken)));
+ return true;
+ }
+ void endVisit(AST::UiImport *el) override {
+ Node::accept(el->version, this);
+ stop(u"UiImport");
+ }
+
+ bool visit(UiPublicMember *el) override {
+ QString typeStr = ((el->type == UiPublicMember::Signal) ? QLatin1String("Signal") :
+ (el->type == UiPublicMember::Property) ? QLatin1String("Property") : QLatin1String("Unexpected(%1)").arg(QString::number(el->type)));
+ start(QLatin1String("UiPublicMember type=%1 typeModifier=%2 name=%3 isDefaultMember=%4 "
+ "isReadonlyMember=%5 isRequired=%6 "
+ "defaultToken=%7 readonlyToken=%8 propertyToken=%9 requiredToken=%10 "
+ "typeModifierToken=%11 typeToken=%12 "
+ "identifierToken=%13 colonToken=%14%15")
+ .arg(quotedString(typeStr), quotedString(el->typeModifier),
+ quotedString(el->name), boolStr(el->isDefaultMember()),
+ boolStr(el->isReadonly()), boolStr(el->isRequired()),
+ loc(el->defaultToken()), loc(el->readonlyToken()),
+ loc(el->propertyToken()), loc(el->requiredToken()),
+ loc(el->typeModifierToken), loc(el->typeToken),
+ loc(el->identifierToken), loc(el->colonToken),
+ semicolonToken(el->semicolonToken)));
+ if (!noAnnotations()) // put annotations inside the node they refer to
+ Node::accept(el->annotations, this);
+ Node::accept(el->memberType, this);
+ return true;
+ }
+ void endVisit(AST::UiPublicMember *el) override {
+ Node::accept(el->parameters, this);
+ stop(u"UiPublicMember");
+ }
+
+ bool visit(AST::UiSourceElement *el) override {
+ start(u"UiSourceElement");
+ if (!noAnnotations()) // put annotations inside the node they refer to
+ Node::accept(el->annotations, this);
+ return true;
+ }
+ void endVisit(AST::UiSourceElement *) override { stop(u"UiSourceElement"); }
+
+ bool visit(AST::UiObjectDefinition *el) override {
+ start(u"UiObjectDefinition");
+ if (!noAnnotations()) // put annotations inside the node they refer to
+ Node::accept(el->annotations, this);
+ return true;
+ }
+ void endVisit(AST::UiObjectDefinition *) override { stop(u"UiObjectDefinition"); }
+
+ bool visit(AST::UiObjectInitializer *el) override {
+ start(QLatin1String("UiObjectInitializer lbraceToken=%1 rbraceToken=%2")
+ .arg(loc(el->lbraceToken), loc(el->rbraceToken)));
+ return true;
+ }
+ void endVisit(AST::UiObjectInitializer *) override { stop(u"UiObjectInitializer"); }
+
+ bool visit(AST::UiObjectBinding *el) override {
+ start(QLatin1String("UiObjectBinding colonToken=%1 hasOnToken=%2")
+ .arg(loc(el->colonToken), boolStr(el->hasOnToken)));
+ if (!noAnnotations()) // put annotations inside the node they refer to
+ Node::accept(el->annotations, this);
+ return true;
+ }
+ void endVisit(AST::UiObjectBinding *) override { stop(u"UiObjectBinding"); }
+
+ bool visit(AST::UiScriptBinding *el) override {
+ start(QLatin1String("UiScriptBinding colonToken=%1")
+ .arg(loc(el->colonToken)));
+ if (!noAnnotations()) // put annotations inside the node they refer to
+ Node::accept(el->annotations, this);
+ return true;
+ }
+ void endVisit(AST::UiScriptBinding *) override { stop(u"UiScriptBinding"); }
+
+ bool visit(AST::UiArrayBinding *el) override {
+ start(QLatin1String("UiArrayBinding colonToken=%1 lbracketToken=%2 rbracketToken=%3")
+ .arg(loc(el->colonToken), loc(el->lbracketToken), loc(el->rbracketToken)));
+ if (!noAnnotations()) // put annotations inside the node they refer to
+ Node::accept(el->annotations, this);
+ return true;
+ }
+ void endVisit(AST::UiArrayBinding *) override { stop(u"UiArrayBinding"); }
+
+ bool visit(AST::UiParameterList *el) override {
+ start(QLatin1String("UiParameterList name=%1 commaToken=%2 propertyTypeToken=%3 identifierToken=%4 colonToken=%5")
+ .arg(quotedString(el->name), loc(el->commaToken), loc(el->propertyTypeToken), loc(el->identifierToken), loc(el->colonToken)));
+ Node::accept(el->type, this);
+ return true;
+ }
+ void endVisit(AST::UiParameterList *el) override {
+ stop(u"UiParameterList");
+ Node::accept(el->next, this); // put other args at the same level as this one...
+ }
+
+ bool visit(AST::UiObjectMemberList *) override { start(u"UiObjectMemberList"); return true; }
+ void endVisit(AST::UiObjectMemberList *) override { stop(u"UiObjectMemberList"); }
+
+ bool visit(AST::UiArrayMemberList *el) override {
+ start(QLatin1String("UiArrayMemberList commaToken=%1")
+ .arg(loc(el->commaToken)));
+ return true;
+ }
+ void endVisit(AST::UiArrayMemberList *) override { stop(u"UiArrayMemberList"); }
+
+ bool visit(AST::UiQualifiedId *el) override {
+ start(QLatin1String("UiQualifiedId name=%1 identifierToken=%2")
+ .arg(quotedString(el->name), loc(el->identifierToken)));
+ Node::accept(el->next, this);
+ return true;
+ }
+ void endVisit(AST::UiQualifiedId *) override { stop(u"UiQualifiedId"); }
+
+ bool visit(AST::UiEnumDeclaration *el) override {
+ start(QLatin1String("UiEnumDeclaration enumToken=%1 rbraceToken=%2 name=%3")
+ .arg(loc(el->enumToken), loc(el->rbraceToken), quotedString(el->name)));
+ if (!noAnnotations()) // put annotations inside the node they refer to
+ Node::accept(el->annotations, this);
+ return true;
+ }
+ void endVisit(AST::UiEnumDeclaration *) override { stop(u"UiEnumDeclaration"); }
+
+ bool visit(AST::UiEnumMemberList *el) override {
+ start(QLatin1String("UiEnumMemberList member=%1 value=%2 memberToken=%3 valueToken=%4")
+ .arg(quotedString(el->member), quotedString(QString::number(el->value)), loc(el->memberToken), loc(el->valueToken)));
+ return true;
+ }
+ void endVisit(AST::UiEnumMemberList *el) override {
+ stop(u"UiEnumMemberList");
+ Node::accept(el->next, this); // put other enum members at the same level as this one...
+ }
+
+ bool visit(AST::UiVersionSpecifier *el) override {
+ start(QLatin1String("UiVersionSpecifier majorVersion=%1 minorVersion=%2 majorToken=%3 minorToken=%4")
+ .arg(quotedString(QString::number(el->version.majorVersion())),
+ quotedString(QString::number(el->version.minorVersion())),
+ loc(el->majorToken), loc(el->minorToken)));
+ return true;
+ }
+ void endVisit(AST::UiVersionSpecifier *) override { stop(u"UiVersionSpecifier"); }
+
+ bool visit(AST::UiInlineComponent *el) override {
+ start(QLatin1String("UiInlineComponent name=%1 componentToken=%2")
+ .arg(quotedString(el->name), loc(el->componentToken)));
+ if (!noAnnotations()) // put annotations inside the node they refer to
+ Node::accept(el->annotations, this);
+ return true;
+ }
+ void endVisit(AST::UiInlineComponent *) override { stop(u"UiInlineComponent"); }
+
+ bool visit(UiRequired *el) override {
+ start(QLatin1String("UiRequired name=%1 requiredToken=%2%3")
+ .arg(quotedString(el->name), loc(el->requiredToken),
+ semicolonToken(el->semicolonToken)));
+ return true;
+ }
+ void endVisit(UiRequired *) override { stop(u"UiRequired"); }
+
+ bool visit(UiAnnotation *) override {
+ start(u"UiAnnotation");
+ return true;
+ }
+ void endVisit(UiAnnotation *) override { stop(u"UiAnnotation"); }
+
+ bool visit(UiAnnotationList *) override {
+ start(u"UiAnnotationList");
+ return true;
+ }
+ void endVisit(UiAnnotationList *) override { stop(u"UiAnnotationList"); }
+
+ // QQmlJS
+ bool visit(AST::TypeExpression *) override {
+ start(u"TypeExpression");
+ return true;
+ }
+ void endVisit(AST::TypeExpression *) override { stop(u"TypeExpression"); }
+
+ bool visit(AST::ThisExpression *el) override {
+ start(QLatin1String("ThisExpression thisToken=%1")
+ .arg(loc(el->thisToken)));
+ return true;
+ }
+ void endVisit(AST::ThisExpression *) override { stop(u"ThisExpression"); }
+
+ bool visit(AST::IdentifierExpression *el) override {
+ start(QLatin1String("IdentifierExpression name=%1 identifierToken=%2")
+ .arg(quotedString(el->name), loc(el->identifierToken)));
+ return true;
+ }
+ void endVisit(AST::IdentifierExpression *) override { stop(u"IdentifierExpression"); }
+
+ bool visit(AST::NullExpression *el) override {
+ start(QLatin1String("NullExpression nullToken=%1")
+ .arg(loc(el->nullToken)));
+ return true;
+ }
+ void endVisit(AST::NullExpression *) override { stop(u"NullExpression"); }
+
+ bool visit(AST::TrueLiteral *el) override {
+ start(QLatin1String("TrueLiteral trueToken=%1")
+ .arg(loc(el->trueToken)));
+ return true;
+ }
+ void endVisit(AST::TrueLiteral *) override { stop(u"TrueLiteral"); }
+
+ bool visit(AST::FalseLiteral *el) override {
+ start(QLatin1String("FalseLiteral falseToken=%1")
+ .arg(loc(el->falseToken)));
+ return true;
+ }
+ void endVisit(AST::FalseLiteral *) override { stop(u"FalseLiteral"); }
+
+ bool visit(AST::SuperLiteral *el) override {
+ start(QLatin1String("SuperLiteral superToken=%1")
+ .arg(loc(el->superToken)));
+ return true;
+ }
+ void endVisit(AST::SuperLiteral *) override { stop(u"SuperLiteral"); }
+
+ bool visit(AST::StringLiteral *el) override {
+ start(QLatin1String("StringLiteral value=%1 literalToken=%2")
+ .arg(quotedString(el->value), loc(el->literalToken)));
+ return true;
+ }
+ void endVisit(AST::StringLiteral *) override { stop(u"StringLiteral"); }
+
+ bool visit(AST::TemplateLiteral *el) override {
+ start(QLatin1String("TemplateLiteral value=%1 rawValue=%2 literalToken=%3")
+ .arg(quotedString(el->value), quotedString(el->rawValue), loc(el->literalToken)));
+ Node::accept(el->expression, this);
+ return true;
+ }
+ void endVisit(AST::TemplateLiteral *) override { stop(u"TemplateLiteral"); }
+
+ bool visit(AST::NumericLiteral *el) override {
+ start(QLatin1String("NumericLiteral value=%1 literalToken=%2")
+ .arg(quotedString(QString::number(el->value)), loc(el->literalToken)));
+ return true;
+ }
+ void endVisit(AST::NumericLiteral *) override { stop(u"NumericLiteral"); }
+
+ bool visit(AST::RegExpLiteral *el) override {
+ start(QLatin1String("RegExpLiteral pattern=%1 flags=%2 literalToken=%3")
+ .arg(quotedString(el->pattern), quotedString(QString::number(el->flags, 16)), loc(el->literalToken)));
+ return true;
+ }
+ void endVisit(AST::RegExpLiteral *) override { stop(u"RegExpLiteral"); }
+
+ bool visit(AST::ArrayPattern *el) override {
+ start(QLatin1String("ArrayPattern lbracketToken=%1 commaToken=%2 rbracketToken=%3 parseMode=%4")
+ .arg(loc(el->lbracketToken),loc(el->commaToken),loc(el->rbracketToken), quotedString(QString::number(el->parseMode, 16))));
+ return true;
+ }
+ void endVisit(AST::ArrayPattern *) override { stop(u"ArrayPattern"); }
+
+ bool visit(AST::ObjectPattern *el) override {
+ start(QLatin1String("ObjectPattern lbraceToken=%1 rbraceToken=%2 parseMode=%3")
+ .arg(loc(el->lbraceToken), loc(el->rbraceToken), quotedString(QString::number(el->parseMode, 16))));
+ return true;
+ }
+ void endVisit(AST::ObjectPattern *) override { stop(u"ObjectPattern"); }
+
+ bool visit(AST::PatternElementList *) override { start(u"PatternElementList"); return true; }
+ void endVisit(AST::PatternElementList *) override { stop(u"PatternElementList"); }
+
+ bool visit(AST::PatternPropertyList *) override { start(u"PatternPropertyList"); return true; }
+ void endVisit(AST::PatternPropertyList *) override { stop(u"PatternPropertyList"); }
+
+ bool visit(AST::PatternElement *el) override {
+ start(QLatin1String("PatternElement identifierToken=%1 bindingIdentifier=%2 type=%3 scope=%4 isForDeclaration=%5")
+ .arg(loc(el->identifierToken), quotedString(el->bindingIdentifier), quotedString(QString::number(el->type, 16)),
+ quotedString(QString::number(static_cast<int>(el->scope), 16)), boolStr(el->isForDeclaration)));
+ return true;
+ }
+ void endVisit(AST::PatternElement *) override { stop(u"PatternElement"); }
+
+ bool visit(AST::PatternProperty *el) override {
+ start(QLatin1String("PatternProperty identifierToken=%1 bindingIdentifier=%2 type=%3 scope=%4 isForDeclaration=%5 colonToken=%6")
+ .arg(loc(el->identifierToken), quotedString(el->bindingIdentifier), quotedString(QString::number(el->type, 16)),
+ quotedString(QString::number(static_cast<int>(el->scope), 16)), boolStr(el->isForDeclaration), loc(el->colonToken)));
+ return true;
+ }
+ void endVisit(AST::PatternProperty *) override { stop(u"PatternProperty"); }
+
+ bool visit(AST::Elision *el) override {
+ start(QLatin1String("Elision commaToken=%1")
+ .arg(loc(el->commaToken)));
+ return true;
+ }
+ void endVisit(AST::Elision *el) override {
+ stop(u"Elision");
+ Node::accept(el->next, this); // emit other elisions at the same level
+ }
+
+ bool visit(AST::NestedExpression *el) override {
+ start(QLatin1String("NestedExpression lparenToken=%1 rparenToken=%2")
+ .arg(loc(el->lparenToken), loc(el->rparenToken)));
+ return true;
+ }
+ void endVisit(AST::NestedExpression *) override { stop(u"NestedExpression"); }
+
+ bool visit(AST::IdentifierPropertyName *el) override {
+ if (options & AstDumperOption::SloppyCompare)
+ start(QLatin1String("StringLiteralOrIdentifierPropertyName id=%1")
+ .arg(quotedString(el->id)));
+ else
+ start(QLatin1String("IdentifierPropertyName id=%1 propertyNameToken=%2")
+ .arg(quotedString(el->id), loc(el->propertyNameToken)));
+ return true;
+ }
+ void endVisit(AST::IdentifierPropertyName *) override {
+ if (options & AstDumperOption::SloppyCompare)
+ stop(u"StringLiteralOrIdentifierPropertyName");
+ else
+ stop(u"IdentifierPropertyName");
+ }
+
+ bool visit(AST::StringLiteralPropertyName *el) override {
+ if (options & AstDumperOption::SloppyCompare)
+ start(QLatin1String("StringLiteralOrIdentifierPropertyName id=%1")
+ .arg(quotedString(el->id)));
+ else
+ start(QLatin1String("StringLiteralPropertyName id=%1 propertyNameToken=%2")
+ .arg(quotedString(el->id), loc(el->propertyNameToken)));
+ return true;
+ }
+ void endVisit(AST::StringLiteralPropertyName *) override {
+ if (options & AstDumperOption::SloppyCompare)
+ stop(u"StringLiteralOrIdentifierPropertyName");
+ else
+ stop(u"StringLiteralPropertyName");
+ }
+
+ bool visit(AST::NumericLiteralPropertyName *el) override {
+ start(QLatin1String("NumericLiteralPropertyName id=%1 propertyNameToken=%2")
+ .arg(quotedString(QString::number(el->id)),loc(el->propertyNameToken)));
+ return true;
+ }
+ void endVisit(AST::NumericLiteralPropertyName *) override { stop(u"NumericLiteralPropertyName"); }
+
+ bool visit(AST::ComputedPropertyName *) override {
+ start(u"ComputedPropertyName");
+ return true;
+ }
+ void endVisit(AST::ComputedPropertyName *) override { stop(u"ComputedPropertyName"); }
+
+ bool visit(AST::ArrayMemberExpression *el) override {
+ start(QLatin1String("ArrayMemberExpression lbraketToken=%1 rbraketToken=%2")
+ .arg(loc(el->lbracketToken), loc(el->rbracketToken)));
+ return true;
+ }
+ void endVisit(AST::ArrayMemberExpression *) override { stop(u"ArrayMemberExpression"); }
+
+ bool visit(AST::FieldMemberExpression *el) override {
+ start(QLatin1String("FieldMemberExpression name=%1 dotToken=%2 identifierToken=%3")
+ .arg(quotedString(el->name), loc(el->dotToken), loc(el->identifierToken)));
+ return true;
+ }
+ void endVisit(AST::FieldMemberExpression *) override { stop(u"FieldMemberExpression"); }
+
+ bool visit(AST::TaggedTemplate *) override {
+ start(u"TaggedTemplate");
+ return true;
+ }
+ void endVisit(AST::TaggedTemplate *) override { stop(u"TaggedTemplate"); }
+
+ bool visit(AST::NewMemberExpression *el) override {
+ start(QLatin1String("NewMemberExpression newToken=%1 lparenToken=%2 rparenToken=%3")
+ .arg(loc(el->newToken), loc(el->lparenToken), loc(el->rparenToken)));
+ return true;
+ }
+ void endVisit(AST::NewMemberExpression *) override { stop(u"NewMemberExpression"); }
+
+ bool visit(AST::NewExpression *el) override {
+ start(QLatin1String("NewExpression newToken=%1")
+ .arg(loc(el->newToken)));
+ return true;
+ }
+ void endVisit(AST::NewExpression *) override { stop(u"NewExpression"); }
+
+ bool visit(AST::CallExpression *el) override {
+ start(QLatin1String("CallExpression lparenToken=%1 rparenToken=%2")
+ .arg(loc(el->lparenToken), loc(el->rparenToken)));
+ return true;
+ }
+ void endVisit(AST::CallExpression *) override { stop(u"CallExpression"); }
+
+ bool visit(AST::ArgumentList *el) override {
+ start(QLatin1String("ArgumentList commaToken=%1 isSpreadElement=%2")
+ .arg(loc(el->commaToken), boolStr(el->isSpreadElement)));
+ return true;
+ }
+ void endVisit(AST::ArgumentList *) override { stop(u"ArgumentList"); }
+
+ bool visit(AST::PostIncrementExpression *el) override {
+ start(QLatin1String("PostIncrementExpression incrementToken=%1")
+ .arg(loc(el->incrementToken)));
+ return true;
+ }
+ void endVisit(AST::PostIncrementExpression *) override { stop(u"PostIncrementExpression"); }
+
+ bool visit(AST::PostDecrementExpression *el) override {
+ start(QLatin1String("PostDecrementExpression decrementToken=%1")
+ .arg(loc(el->decrementToken)));
+ return true;
+ }
+ void endVisit(AST::PostDecrementExpression *) override { stop(u"PostDecrementExpression"); }
+
+ bool visit(AST::DeleteExpression *el) override {
+ start(QLatin1String("DeleteExpression deleteToken=%1")
+ .arg(loc(el->deleteToken)));
+ return true;
+ }
+ void endVisit(AST::DeleteExpression *) override { stop(u"DeleteExpression"); }
+
+ bool visit(AST::VoidExpression *el) override {
+ start(QLatin1String("VoidExpression voidToken=%1")
+ .arg(loc(el->voidToken)));
+ return true;
+ }
+ void endVisit(AST::VoidExpression *) override { stop(u"VoidExpression"); }
+
+ bool visit(AST::TypeOfExpression *el) override {
+ start(QLatin1String("TypeOfExpression typeofToken=%1")
+ .arg(loc(el->typeofToken)));
+ return true;
+ }
+ void endVisit(AST::TypeOfExpression *) override { stop(u"TypeOfExpression"); }
+
+ bool visit(AST::PreIncrementExpression *el) override {
+ start(QLatin1String("PreIncrementExpression incrementToken=%1")
+ .arg(loc(el->incrementToken)));
+ return true;
+ }
+ void endVisit(AST::PreIncrementExpression *) override { stop(u"PreIncrementExpression"); }
+
+ bool visit(AST::PreDecrementExpression *el) override {
+ start(QLatin1String("PreDecrementExpression decrementToken=%1")
+ .arg(loc(el->decrementToken)));
+ return true;
+ }
+ void endVisit(AST::PreDecrementExpression *) override { stop(u"PreDecrementExpression"); }
+
+ bool visit(AST::UnaryPlusExpression *el) override {
+ start(QLatin1String("UnaryPlusExpression plusToken=%1")
+ .arg(loc(el->plusToken)));
+ return true;
+ }
+ void endVisit(AST::UnaryPlusExpression *) override { stop(u"UnaryPlusExpression"); }
+
+ bool visit(AST::UnaryMinusExpression *el) override {
+ start(QLatin1String("UnaryMinusExpression minusToken=%1")
+ .arg(loc(el->minusToken)));
+ return true;
+ }
+ void endVisit(AST::UnaryMinusExpression *) override { stop(u"UnaryMinusExpression"); }
+
+ bool visit(AST::TildeExpression *el) override {
+ start(QLatin1String("TildeExpression tildeToken=%1")
+ .arg(loc(el->tildeToken)));
+ return true;
+ }
+ void endVisit(AST::TildeExpression *) override { stop(u"TildeExpression"); }
+
+ bool visit(AST::NotExpression *el) override {
+ start(QLatin1String("NotExpression notToken=%1")
+ .arg(loc(el->notToken)));
+ return true;
+ }
+ void endVisit(AST::NotExpression *) override { stop(u"NotExpression"); }
+
+ bool visit(AST::BinaryExpression *el) override {
+ start(QLatin1String("BinaryExpression op=%1 operatorToken=%2")
+ .arg(quotedString(QString::number(el->op,16)), loc(el->operatorToken)));
+ return true;
+ }
+ void endVisit(AST::BinaryExpression *) override { stop(u"BinaryExpression"); }
+
+ bool visit(AST::ConditionalExpression *el) override {
+ start(QLatin1String("ConditionalExpression questionToken=%1 colonToken=%2")
+ .arg(loc(el->questionToken), loc(el->colonToken)));
+ return true;
+ }
+ void endVisit(AST::ConditionalExpression *) override { stop(u"ConditionalExpression"); }
+
+ bool visit(AST::Expression *el) override {
+ start(QLatin1String("Expression commaToken=%1")
+ .arg(loc(el->commaToken)));
+ return true;
+ }
+ void endVisit(AST::Expression *) override { stop(u"Expression"); }
+
+ bool visit(AST::Block *el) override {
+ start(QLatin1String("Block lbraceToken=%1 rbraceToken=%2")
+ .arg(loc(el->lbraceToken), loc(el->rbraceToken)));
+ return true;
+ }
+ void endVisit(AST::Block *) override { stop(u"Block"); }
+
+ bool visit(AST::StatementList *) override {
+ start(u"StatementList");
+ return true;
+ }
+ void endVisit(AST::StatementList *) override { stop(u"StatementList"); }
+
+ bool visit(AST::VariableStatement *el) override {
+ start(QLatin1String("VariableStatement declarationKindToken=%1")
+ .arg(loc(el->declarationKindToken)));
+ return true;
+ }
+ void endVisit(AST::VariableStatement *) override { stop(u"VariableStatement"); }
+
+ bool visit(AST::VariableDeclarationList *el) override {
+ start(QLatin1String("VariableDeclarationList commaToken=%1")
+ .arg(loc(el->commaToken)));
+ return true;
+ }
+ void endVisit(AST::VariableDeclarationList *) override { stop(u"VariableDeclarationList"); }
+
+ bool visit(AST::EmptyStatement *el) override {
+ start(QLatin1String("EmptyStatement%1").arg(semicolonToken(el->semicolonToken)));
+ return true;
+ }
+ void endVisit(AST::EmptyStatement *) override { stop(u"EmptyStatement"); }
+
+ bool visit(AST::ExpressionStatement *el) override {
+ if (options & AstDumperOption::SloppyCompare)
+ start(u"ExpressionStatement");
+ else
+ start(QLatin1String("ExpressionStatement%1").arg(semicolonToken(el->semicolonToken)));
+ return true;
+ }
+ void endVisit(AST::ExpressionStatement *) override { stop(u"ExpressionStatement"); }
+
+ bool visit(AST::IfStatement *el) override {
+ start(QLatin1String("IfStatement ifToken=%1 lparenToken=%2 rparenToken=%3 elseToken=%4")
+ .arg(loc(el->ifToken), loc(el->lparenToken), loc(el->rparenToken), loc(el->elseToken)));
+ return true;
+ }
+ void endVisit(AST::IfStatement *) override { stop(u"IfStatement"); }
+
+ bool visit(AST::DoWhileStatement *el) override {
+ start(QLatin1String(
+ "DoWhileStatement doToken=%1 whileToken=%2 lparenToken=%3 rparenToken=%4%5")
+ .arg(loc(el->doToken), loc(el->whileToken), loc(el->lparenToken),
+ loc(el->rparenToken), semicolonToken(el->semicolonToken)));
+ return true;
+ }
+ void endVisit(AST::DoWhileStatement *) override { stop(u"DoWhileStatement"); }
+
+ bool visit(AST::WhileStatement *el) override {
+ start(QLatin1String("WhileStatement whileToken=%1 lparenToken=%2 rparenToken=%3")
+ .arg(loc(el->whileToken), loc(el->lparenToken), loc(el->rparenToken)));
+ return true;
+ }
+ void endVisit(AST::WhileStatement *) override { stop(u"WhileStatement"); }
+
+ bool visit(AST::ForStatement *el) override {
+ if (options & AstDumperOption::SloppyCompare)
+ start(QLatin1String("ForStatement forToken=%1 lparenToken=%2 rparenToken=%5")
+ .arg(loc(el->forToken), loc(el->lparenToken), loc(el->rparenToken)));
+ else
+ start(QLatin1String("ForStatement forToken=%1 lparenToken=%2 firstSemicolonToken=%3 "
+ "secondSemicolonToken=%4 rparenToken=%5")
+ .arg(loc(el->forToken), loc(el->lparenToken),
+ loc(el->firstSemicolonToken), loc(el->secondSemicolonToken),
+ loc(el->rparenToken)));
+ return true;
+ }
+ void endVisit(AST::ForStatement *) override { stop(u"ForStatement"); }
+
+ bool visit(AST::ForEachStatement *el) override {
+ start(QLatin1String("ForEachStatement forToken=%1 lparenToken=%2 inOfToken=%3 rparenToken=%4 type=%5")
+ .arg(loc(el->forToken), loc(el->lparenToken), loc(el->inOfToken), loc(el->rparenToken), quotedString(QString::number(static_cast<int>(el->type), 16))));
+ return true;
+ }
+ void endVisit(AST::ForEachStatement *) override { stop(u"ForEachStatement"); }
+
+ bool visit(AST::ContinueStatement *el) override {
+ start(QLatin1String("ContinueStatement label=%1 continueToken=%2 identifierToken=%3%4")
+ .arg(quotedString(el->label), loc(el->continueToken),
+ loc(el->identifierToken), semicolonToken(el->semicolonToken)));
+ return true;
+ }
+ void endVisit(AST::ContinueStatement *) override { stop(u"ContinueStatement"); }
+
+ bool visit(AST::BreakStatement *el) override {
+ start(QLatin1String("BreakStatement label=%1 breakToken=%2 identifierToken=%3%4")
+ .arg(quotedString(el->label), loc(el->breakToken), loc(el->identifierToken),
+ semicolonToken(el->semicolonToken)));
+ return true;
+ }
+ void endVisit(AST::BreakStatement *) override { stop(u"BreakStatement"); }
+
+ bool visit(AST::ReturnStatement *el) override {
+ start(QLatin1String("ReturnStatement returnToken=%1%2")
+ .arg(loc(el->returnToken), semicolonToken(el->semicolonToken)));
+ return true;
+ }
+ void endVisit(AST::ReturnStatement *) override { stop(u"ReturnStatement"); }
+
+ bool visit(AST::YieldExpression *el) override {
+ start(QLatin1String("YieldExpression isYieldStar=%1 yieldToken=%2")
+ .arg(boolStr(el->isYieldStar), loc(el->yieldToken)));
+ return true;
+ }
+ void endVisit(AST::YieldExpression *) override { stop(u"YieldExpression"); }
+
+ bool visit(AST::WithStatement *el) override {
+ start(QLatin1String("WithStatement withToken=%1 lparenToken=%2 rparenToken=%3")
+ .arg(loc(el->withToken), loc(el->lparenToken), loc(el->rparenToken)));
+ return true;
+ }
+ void endVisit(AST::WithStatement *) override { stop(u"WithStatement"); }
+
+ bool visit(AST::SwitchStatement *el) override {
+ start(QLatin1String("SwitchStatement switchToken=%1 lparenToken=%2 rparenToken=%3")
+ .arg(loc(el->switchToken), loc(el->lparenToken), loc(el->rparenToken)));
+ return true;
+ }
+ void endVisit(AST::SwitchStatement *) override { stop(u"SwitchStatement"); }
+
+ bool visit(AST::CaseBlock *el) override {
+ start(QLatin1String("CaseBlock lbraceToken=%1 rbraceToken=%2")
+ .arg(loc(el->lbraceToken), loc(el->rbraceToken)));
+ return true;
+ }
+ void endVisit(AST::CaseBlock *) override { stop(u"CaseBlock"); }
+
+ bool visit(AST::CaseClauses *) override {
+ start(u"CaseClauses");
+ return true;
+ }
+ void endVisit(AST::CaseClauses *) override { stop(u"CaseClauses"); }
+
+ bool visit(AST::CaseClause *el) override {
+ start(QLatin1String("CaseClause caseToken=%1 colonToken=%2")
+ .arg(loc(el->caseToken), loc(el->colonToken)));
+ return true;
+ }
+ void endVisit(AST::CaseClause *) override { stop(u"CaseClause"); }
+
+ bool visit(AST::DefaultClause *el) override {
+ start(QLatin1String("DefaultClause defaultToken=%1 colonToken=%2")
+ .arg(loc(el->defaultToken), loc(el->colonToken)));
+ return true;
+ }
+ void endVisit(AST::DefaultClause *) override { stop(u"DefaultClause"); }
+
+ bool visit(AST::LabelledStatement *el) override {
+ start(QLatin1String("LabelledStatement label=%1 identifierToken=%2 colonToken=%3")
+ .arg(quotedString(el->label), loc(el->identifierToken), loc(el->colonToken)));
+ return true;
+ }
+ void endVisit(AST::LabelledStatement *) override { stop(u"LabelledStatement"); }
+
+ bool visit(AST::ThrowStatement *el) override {
+ start(QLatin1String("ThrowStatement throwToken=%1%2")
+ .arg(loc(el->throwToken), semicolonToken(el->semicolonToken)));
+ return true;
+ }
+ void endVisit(AST::ThrowStatement *) override { stop(u"ThrowStatement"); }
+
+ bool visit(AST::TryStatement *el) override {
+ start(QLatin1String("TryStatement tryToken=%1")
+ .arg(loc(el->tryToken)));
+ return true;
+ }
+ void endVisit(AST::TryStatement *) override { stop(u"TryStatement"); }
+
+ bool visit(AST::Catch *el) override {
+ start(QLatin1String("Catch catchToken=%1 lparenToken=%2 identifierToken=%3 rparenToken=%4")
+ .arg(loc(el->catchToken), loc(el->lparenToken), loc(el->identifierToken), loc(el->rparenToken)));
+ return true;
+ }
+ void endVisit(AST::Catch *) override { stop(u"Catch"); }
+
+ bool visit(AST::Finally *el) override {
+ start(QLatin1String("Finally finallyToken=%1")
+ .arg(loc(el->finallyToken)));
+ return true;
+ }
+ void endVisit(AST::Finally *) override { stop(u"Finally"); }
+
+ bool visit(AST::FunctionDeclaration *el) override {
+ start(QLatin1String("FunctionDeclaration name=%1 isArrowFunction=%2 isGenerator=%3 functionToken=%4 "
+ "identifierToken=%5 lparenToken=%6 rparenToken=%7 lbraceToken=%8 rbraceToken=%9")
+ .arg(quotedString(el->name), boolStr(el->isArrowFunction), boolStr(el->isGenerator),
+ loc(el->functionToken, options & AstDumperOption::SloppyCompare),
+ loc(el->identifierToken), loc(el->lparenToken), loc(el->rparenToken), loc(el->lbraceToken),
+ loc(el->rbraceToken)));
+ return true;
+ }
+ void endVisit(AST::FunctionDeclaration *) override { stop(u"FunctionDeclaration"); }
+
+ bool visit(AST::FunctionExpression *el) override {
+ start(QLatin1String("FunctionExpression name=%1 isArrowFunction=%2 isGenerator=%3 "
+ "functionToken=%4 "
+ "identifierToken=%5 lparenToken=%6 rparenToken=%7 lbraceToken=%8 "
+ "rbraceToken=%9")
+ .arg(quotedString(el->name), boolStr(el->isArrowFunction),
+ boolStr(el->isGenerator),
+ loc(el->functionToken, options & AstDumperOption::SloppyCompare),
+ loc(el->identifierToken), loc(el->lparenToken), loc(el->rparenToken),
+ loc(el->lbraceToken), loc(el->rbraceToken)));
+ return true;
+ }
+ void endVisit(AST::FunctionExpression *) override { stop(u"FunctionExpression"); }
+
+ bool visit(AST::FormalParameterList *) override {
+ start(u"FormalParameterList");
+ return true;
+ }
+ void endVisit(AST::FormalParameterList *) override { stop(u"FormalParameterList"); }
+
+ bool visit(AST::ClassExpression *el) override {
+ start(QLatin1String("ClassExpression name=%1 classToken=%2 identifierToken=%3 lbraceToken=%4 rbraceToken=%5")
+ .arg(quotedString(el->name), loc(el->classToken), loc(el->identifierToken), loc(el->lbraceToken), loc(el->rbraceToken)));
+ return true;
+ }
+ void endVisit(AST::ClassExpression *) override { stop(u"ClassExpression"); }
+
+ bool visit(AST::ClassDeclaration *el) override {
+ start(QLatin1String("ClassDeclaration name=%1 classToken=%2 identifierToken=%3 lbraceToken=%4 rbraceToken=%5")
+ .arg(quotedString(el->name), loc(el->classToken), loc(el->identifierToken), loc(el->lbraceToken), loc(el->rbraceToken)));
+ return true;
+ }
+ void endVisit(AST::ClassDeclaration *) override { stop(u"ClassDeclaration"); }
+
+ bool visit(AST::ClassElementList *el) override {
+ start(QLatin1String("ClassElementList isStatic=%1")
+ .arg(boolStr(el->isStatic)));
+ return true;
+ }
+ void endVisit(AST::ClassElementList *) override { stop(u"ClassElementList"); }
+
+ bool visit(AST::Program *) override {
+ start(u"Program");
+ return true;
+ }
+ void endVisit(AST::Program *) override { stop(u"Program"); }
+
+ bool visit(AST::NameSpaceImport *el) override {
+ start(QLatin1String("NameSpaceImport starToken=%1 importedBindingToken=%2 importedBinding=%3")
+ .arg(loc(el->starToken), loc(el->importedBindingToken), quotedString(el->importedBinding)));
+ return true;
+ }
+ void endVisit(AST::NameSpaceImport *) override { stop(u"NameSpaceImport"); }
+
+ bool visit(AST::ImportSpecifier *el) override {
+ start(QLatin1String("ImportSpecifier identifierToken=%1 importedBindingToken=%2 identifier=%3 importedBinding=%4")
+ .arg(loc(el->identifierToken), loc(el->importedBindingToken), quotedString(el->identifier), quotedString(el->importedBinding)));
+ return true;
+ }
+ void endVisit(AST::ImportSpecifier *) override { stop(u"ImportSpecifier"); }
+
+ bool visit(AST::ImportsList *el) override {
+ start(QLatin1String("ImportsList importSpecifierToken=%1")
+ .arg(loc(el->importSpecifierToken)));
+ return true;
+ }
+ void endVisit(AST::ImportsList *) override { stop(u"ImportsList"); }
+
+ bool visit(AST::NamedImports *el) override {
+ start(QLatin1String("NamedImports leftBraceToken=%1 rightBraceToken=%2")
+ .arg(loc(el->leftBraceToken), loc(el->rightBraceToken)));
+ return true;
+ }
+ void endVisit(AST::NamedImports *) override { stop(u"NamedImports"); }
+
+ bool visit(AST::FromClause *el) override {
+ start(QLatin1String("FromClause fromToken=%1 moduleSpecifierToken=%2 moduleSpecifier=%3")
+ .arg(loc(el->fromToken), loc(el->moduleSpecifierToken), quotedString(el->moduleSpecifier)));
+ return true;
+ }
+ void endVisit(AST::FromClause *) override { stop(u"FromClause"); }
+
+ bool visit(AST::ImportClause *el) override {
+ start(QLatin1String("ImportClause importedDefaultBindingToken=%1 importedDefaultBinding=%2")
+ .arg(loc(el->importedDefaultBindingToken), quotedString(el->importedDefaultBinding)));
+ return true;
+ }
+ void endVisit(AST::ImportClause *) override { stop(u"ImportClause"); }
+
+ bool visit(AST::ImportDeclaration *el) override {
+ start(QLatin1String("ImportDeclaration importToken=%1 moduleSpecifierToken=%2 moduleSpecifier=%3")
+ .arg(loc(el->importToken), loc(el->moduleSpecifierToken), quotedString(el->moduleSpecifier)));
+ return true;
+ }
+ void endVisit(AST::ImportDeclaration *) override { stop(u"ImportDeclaration"); }
+
+ bool visit(AST::ExportSpecifier *el) override {
+ start(QLatin1String("ExportSpecifier identifierToken=%1 exportedIdentifierToken=%2 identifier=%3 exportedIdentifier=%4")
+ .arg(loc(el->identifierToken), loc(el->exportedIdentifierToken), quotedString(el->identifier), quotedString(el->exportedIdentifier)));
+ return true;
+ }
+ void endVisit(AST::ExportSpecifier *) override { stop(u"ExportSpecifier"); }
+
+ bool visit(AST::ExportsList *) override {
+ start(u"ExportsList");
+ return true;
+ }
+ void endVisit(AST::ExportsList *) override { stop(u"ExportsList"); }
+
+ bool visit(AST::ExportClause *el) override {
+ start(QLatin1String("ExportClause leftBraceToken=%1 rightBraceToken=%2")
+ .arg(loc(el->leftBraceToken), loc(el->rightBraceToken)));
+ return true;
+ }
+ void endVisit(AST::ExportClause *) override { stop(u"ExportClause"); }
+
+ bool visit(AST::ExportDeclaration *el) override {
+ start(QLatin1String("ExportDeclaration exportToken=%1 exportDefault=%3")
+ .arg(loc(el->exportToken), boolStr(el->exportDefault)));
+ return true;
+ }
+ void endVisit(AST::ExportDeclaration *) override { stop(u"ExportDeclaration"); }
+
+ bool visit(AST::ESModule *) override {
+ start(u"ESModule");
+ return true;
+ }
+ void endVisit(AST::ESModule *) override { stop(u"ESModule"); }
+
+ bool visit(AST::DebuggerStatement *el) override {
+ start(QLatin1String("DebuggerStatement debuggerToken=%1%2")
+ .arg(loc(el->debuggerToken), semicolonToken(el->semicolonToken)));
+ return true;
+ }
+ void endVisit(AST::DebuggerStatement *) override { stop(u"DebuggerStatement"); }
+
+ bool visit(AST::Type *) override {
+ start(u"Type");
+ return true;
+ }
+ void endVisit(AST::Type *) override { stop(u"Type"); }
+
+ bool visit(AST::TypeAnnotation *el) override {
+ start(QLatin1String("TypeAnnotation colonToken=%1")
+ .arg(loc(el->colonToken)));
+ return true;
+ }
+ void endVisit(AST::TypeAnnotation *) override { stop(u"TypeAnnotation"); }
+
+ void throwRecursionDepthError() override {
+ qCWarning(domLog) << "Maximum statement or expression depth exceeded in AstDumper";
+ }
+
+private:
+ // attributes
+ std::function <void (QStringView)> dumper;
+ AstDumperOptions options = AstDumperOption::None;
+ int indent = 0;
+ int baseIndent = 0;
+ function_ref<QStringView(SourceLocation)> loc2str;
+ bool dumpNode(){
+ return options & AstDumperOption::DumpNode;
+ }
+ bool noLocations() {
+ return options & AstDumperOption::NoLocations;
+ }
+ bool noAnnotations() {
+ return options & AstDumperOption::NoAnnotations;
+ }
+};
+
+QDebug operator<<(QDebug d, AST::Node *n) {
+ QDebug noQuote = d.noquote().nospace();
+ AstDumper visitor([&noQuote](QStringView s){ noQuote << s; });
+ Node::accept(n, &visitor);
+ return d;
+}
+
+QString lineDiff(QString s1, QString s2, int nContext) {
+ QTextStream d1(&s1), d2(&s2);
+ QList<QString> preLines(nContext);
+ int nLine = 0;
+ bool same = true;
+ QString l1, l2;
+ while (same && !d1.atEnd() && !d2.atEnd()) {
+ l1=d1.readLine();
+ l2=d2.readLine();
+ if (l1 == l2)
+ preLines[nLine++ % nContext] = l1;
+ else
+ same = false;
+ }
+ QString res;
+ QTextStream ss(&res);
+ if (!same || !d1.atEnd() || !d2.atEnd()) {
+ for (int iline = qMin(nLine, nContext); iline > 0; --iline) {
+ ss << QLatin1String(" ") << preLines[(nLine - iline) % nContext] << QLatin1String("\n");
+ }
+ int iline = 0;
+ if (!same) {
+ ss << QLatin1String("-") << l1 << QLatin1String("\n");
+ ++iline;
+ }
+ if (same && nContext == 0)
+ nContext = 1;
+ for (;iline < nContext && !d1.atEnd(); iline ++) {
+ l1 = d1.readLine();
+ ss << QLatin1String("-") << l1 << QLatin1String("\n");
+ }
+ iline = 0;
+ if (!same) {
+ ss << QLatin1String("+") << l2 << QLatin1String("\n");
+ ++iline;
+ }
+ for (;iline < nContext && !d2.atEnd(); iline ++) {
+ l2 = d2.readLine();
+ ss << QLatin1String("+") << l2 << QLatin1String("\n");
+ }
+ }
+ return res;
+}
+
+QString astNodeDiff(AST::Node *n1, AST::Node *n2, int nContext, AstDumperOptions opt, int indent,
+ function_ref<QStringView(SourceLocation)>loc2str1,
+ function_ref<QStringView(SourceLocation)>loc2str2) {
+ QString s1, s2;
+ QTextStream d1(&s1), d2(&s2);
+ AstDumper visitor1=AstDumper([&d1](QStringView s){ d1 << s; }, opt, indent, 0, loc2str1);
+ AstDumper visitor2=AstDumper([&d2](QStringView s){ d2 << s; }, opt, indent, 0, loc2str2);
+ Node::accept(n1, &visitor1);
+ Node::accept(n2, &visitor2);
+ d1.flush();
+ d2.flush();
+ return lineDiff(s1, s2, nContext);
+}
+
+void astNodeDumper(const Sink &s, Node *n, AstDumperOptions opt, int indent, int baseIndent,
+ function_ref<QStringView(SourceLocation)>loc2str)
+{
+ AstDumper visitor=AstDumper(s, opt, indent, baseIndent, loc2str);
+ Node::accept(n, &visitor);
+}
+
+QString astNodeDump(Node *n, AstDumperOptions opt, int indent, int baseIndent,
+ function_ref<QStringView(SourceLocation)>loc2str)
+{
+ return dumperToString(
+ [n, opt, indent, baseIndent, loc2str = std::move(loc2str)](const Sink &s) {
+ astNodeDumper(s, n, opt, indent, baseIndent, std::move(loc2str));
+ });
+}
+
+} // end namespace Dom
+} // end namespace QQmlJS
+
+QT_END_NAMESPACE
diff --git a/src/qmldom/qqmldomastdumper_p.h b/src/qmldom/qqmldomastdumper_p.h
new file mode 100644
index 0000000000..542f81b1dd
--- /dev/null
+++ b/src/qmldom/qqmldomastdumper_p.h
@@ -0,0 +1,56 @@
+// Copyright (C) 2020 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+
+#ifndef QQMLDOMASTDUMPER_P_H
+#define QQMLDOMASTDUMPER_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 "qqmldomconstants_p.h"
+#include "qqmldomstringdumper_p.h"
+
+#include <QtQml/private/qqmljsglobal_p.h>
+#include <QtQml/private/qqmljsastvisitor_p.h>
+#include <QtCore/QString>
+
+QT_BEGIN_NAMESPACE
+class QDebug;
+
+namespace QQmlJS {
+namespace Dom {
+
+inline QStringView noStr(SourceLocation)
+{
+ return QStringView();
+}
+
+QMLDOM_EXPORT QString lineDiff(QString s1, QString s2, int nContext);
+QMLDOM_EXPORT QString astNodeDiff(AST::Node *n1, AST::Node *n2, int nContext = 3,
+ AstDumperOptions opt = AstDumperOption::None, int indent = 0,
+ function_ref<QStringView(SourceLocation)> loc2str1 = noStr,
+ function_ref<QStringView(SourceLocation)> loc2str2 = noStr);
+QMLDOM_EXPORT void astNodeDumper(const Sink &s, AST::Node *n, AstDumperOptions opt = AstDumperOption::None,
+ int indent = 1, int baseIndent = 0,
+ function_ref<QStringView(SourceLocation)> loc2str = noStr);
+QMLDOM_EXPORT QString astNodeDump(AST::Node *n, AstDumperOptions opt = AstDumperOption::None,
+ int indent = 1, int baseIndent = 0,
+ function_ref<QStringView(SourceLocation)> loc2str = noStr);
+
+QMLDOM_EXPORT QDebug operator<<(QDebug d, AST::Node *n);
+
+} // namespace Dom
+} // namespace AST
+
+QT_END_NAMESPACE
+
+#endif // QQMLDOMASTDUMPER_P_H
diff --git a/src/qmldom/qqmldomattachedinfo.cpp b/src/qmldom/qqmldomattachedinfo.cpp
new file mode 100644
index 0000000000..e86a2782a6
--- /dev/null
+++ b/src/qmldom/qqmldomattachedinfo.cpp
@@ -0,0 +1,345 @@
+// Copyright (C) 2021 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+#include "qqmldom_fwd_p.h"
+#include "qqmldomlinewriter_p.h"
+#include "qqmldomelements_p.h"
+#include "qqmldompath_p.h"
+
+QT_BEGIN_NAMESPACE
+namespace QQmlJS {
+namespace Dom {
+
+using namespace Qt::StringLiterals;
+
+
+/*!
+\internal
+\class QQmlJS::Dom::FileLocations
+\brief Represents and maintains a mapping between elements and their location in a file
+
+The location information is attached to the element it refers to via AttachedInfo
+There are static methods to simplify the handling of the tree of AttachedInfo.
+
+Attributes:
+\list
+\li fullRegion: A location guaranteed to include this element, its comments, and all its sub elements
+\li regions: a map with locations of regions of this element, the empty string is the default region
+ of this element
+\li preCommentLocations: locations of the comments before this element
+\li postCommentLocations: locations of the comments after this element
+\endlist
+
+\sa QQmlJs::Dom::AttachedInfo
+*/
+bool FileLocations::iterateDirectSubpaths(const DomItem &self, DirectVisitor visitor) const
+{
+ bool cont = true;
+ cont = cont && self.dvValueLazyField(visitor, Fields::fullRegion, [this]() {
+ return sourceLocationToQCborValue(fullRegion);
+ });
+ cont = cont && self.dvItemField(visitor, Fields::regions, [this, &self]() -> DomItem {
+ const Path pathFromOwner = self.pathFromOwner().field(Fields::regions);
+ auto map = Map::fromFileRegionMap(pathFromOwner, regions);
+ return self.subMapItem(map);
+ });
+ cont = cont
+ && self.dvItemField(visitor, Fields::preCommentLocations, [this, &self]() -> DomItem {
+ const Path pathFromOwner =
+ self.pathFromOwner().field(Fields::preCommentLocations);
+ auto map = Map::fromFileRegionListMap(pathFromOwner, preCommentLocations);
+ return self.subMapItem(map);
+ });
+ cont = cont
+ && self.dvItemField(visitor, Fields::postCommentLocations, [this, &self]() -> DomItem {
+ const Path pathFromOwner =
+ self.pathFromOwner().field(Fields::postCommentLocations);
+ auto map = Map::fromFileRegionListMap(pathFromOwner, postCommentLocations);
+ return self.subMapItem(map);
+ });
+ return cont;
+}
+
+FileLocations::Tree FileLocations::createTree(const Path &basePath){
+ return AttachedInfoT<FileLocations>::createTree(basePath);
+}
+
+FileLocations::Tree FileLocations::ensure(
+ const FileLocations::Tree &base, const Path &basePath, AttachedInfo::PathType pType)
+{
+ return AttachedInfoT<FileLocations>::ensure(base, basePath, pType);
+}
+
+/*!
+\internal
+Allows to query information about the FileLocations::Tree obtained from item, such as path of
+the Tree root in the Dom, the path of this item's Tree in the Dom, and so on.
+
+\note You can use \c{qDebug() << item.path(FileLocations::findAttachedInfo(item).foundTreePath)} or
+\c{item.path(FileLocations::findAttachedInfo(item).foundTreePath).toString()} to print out the Tree
+of item, for example, as Tree's cannot be printed when outside the Dom.
+*/
+AttachedInfoLookupResult<FileLocations::Tree>
+FileLocations::findAttachedInfo(const DomItem &item)
+{
+ return AttachedInfoT<FileLocations>::findAttachedInfo(item, Fields::fileLocationsTree);
+}
+
+/*!
+ \internal
+ Returns the tree corresponding to a DomItem.
+ */
+FileLocations::Tree FileLocations::treeOf(const DomItem &item)
+{
+ return findAttachedInfo(item).foundTree;
+}
+
+/*!
+ \internal
+ Returns the filelocation Info corresponding to a DomItem.
+ */
+const FileLocations *FileLocations::fileLocationsOf(const DomItem &item)
+{
+ if (const FileLocations::Tree &t = treeOf(item))
+ return &(t->info());
+ return nullptr;
+}
+
+void FileLocations::updateFullLocation(const FileLocations::Tree &fLoc, SourceLocation loc)
+{
+ Q_ASSERT(fLoc);
+ if (loc != SourceLocation()) {
+ FileLocations::Tree p = fLoc;
+ while (p) {
+ SourceLocation &l = p->info().fullRegion;
+ if (loc.begin() < l.begin() || loc.end() > l.end()) {
+ l = combine(l, loc);
+ p->info().regions[MainRegion] = l;
+ } else {
+ break;
+ }
+ p = p->parent();
+ }
+ }
+}
+
+// Adding a new region to file location regions might break down qmlformat because
+// comments might be linked to new region undesirably. We might need to add an
+// exception to AstRangesVisitor::shouldSkipRegion when confronted those cases.
+void FileLocations::addRegion(const FileLocations::Tree &fLoc, FileLocationRegion region,
+ SourceLocation loc)
+{
+ Q_ASSERT(fLoc);
+ fLoc->info().regions[region] = loc;
+ updateFullLocation(fLoc, loc);
+}
+
+SourceLocation FileLocations::region(const FileLocations::Tree &fLoc, FileLocationRegion region)
+{
+ Q_ASSERT(fLoc);
+ const auto &regions = fLoc->info().regions;
+ if (auto it = regions.constFind(region); it != regions.constEnd() && it->isValid()) {
+ return *it;
+ }
+
+ if (region == MainRegion)
+ return fLoc->info().fullRegion;
+
+ return SourceLocation{};
+}
+
+/*!
+\internal
+\class QQmlJS::Dom::AttachedInfo
+\brief Attached info creates a tree to attach extra info to DomItems
+
+Normally one uses the template AttachedInfoT<SpecificInfoToAttach>
+
+static methods
+Attributes:
+\list
+\li parent: parent AttachedInfo in tree (might be empty)
+\li subItems: subItems of the tree (path -> AttachedInfo)
+\li infoItem: the attached information
+\endlist
+
+\sa QQmlJs::Dom::AttachedInfo
+*/
+
+bool AttachedInfo::iterateDirectSubpaths(const DomItem &self, DirectVisitor visitor) const
+{
+ bool cont = true;
+ if (Ptr p = parent())
+ cont = cont && self.dvItemField(visitor, Fields::parent, [&self, p]() {
+ return self.copy(p, self.m_ownerPath.dropTail(2), p.get());
+ });
+ cont = cont
+ && self.dvValueLazyField(visitor, Fields::path, [this]() { return path().toString(); });
+ cont = cont && self.dvItemField(visitor, Fields::subItems, [this, &self]() {
+ return self.subMapItem(Map(
+ Path::Field(Fields::subItems),
+ [this](const DomItem &map, const QString &key) {
+ Path p = Path::fromString(key);
+ return map.copy(m_subItems.value(p), map.canonicalPath().key(key));
+ },
+ [this](const DomItem &) {
+ QSet<QString> res;
+ for (const auto &p : m_subItems.keys())
+ res.insert(p.toString());
+ return res;
+ },
+ QLatin1String("AttachedInfo")));
+ });
+ cont = cont && self.dvItemField(visitor, Fields::infoItem, [&self, this]() {
+ return infoItem(self);
+ });
+ return cont;
+}
+
+AttachedInfo::AttachedInfo(const AttachedInfo &o):
+ OwningItem(o),
+ m_parent(o.m_parent)
+{
+}
+
+/*!
+ \brief
+ Returns that the AttachedInfo corresponding to the given path, creating it if it does not exists.
+
+ The path might be either a relative path or a canonical path, as specified by the PathType
+*/
+AttachedInfo::Ptr AttachedInfo::ensure(
+ const AttachedInfo::Ptr &self, const Path &path, AttachedInfo::PathType pType){
+ Path relative;
+ switch (pType) {
+ case PathType::Canonical: {
+ if (!path)
+ return nullptr;
+ Q_ASSERT(self);
+ Path removed = path.mid(0, self->path().length());
+ Q_ASSERT(removed == self->path());
+ relative = path.mid(self->path().length());
+ } break;
+ case PathType::Relative:
+ Q_ASSERT(self);
+ relative = path;
+ break;
+ }
+ Ptr res = self;
+ for (const auto &p : std::as_const(relative)) {
+ if (AttachedInfo::Ptr subEl = res->m_subItems.value(p)) {
+ res = subEl;
+ } else {
+ AttachedInfo::Ptr newEl = res->instantiate(res, p);
+ res->m_subItems.insert(p, newEl);
+ res = newEl;
+ }
+ }
+ return res;
+}
+
+AttachedInfo::Ptr AttachedInfo::find(
+ const AttachedInfo::Ptr &self, const Path &p, AttachedInfo::PathType pType)
+{
+ Path rest;
+ if (pType == PathType::Canonical) {
+ if (!self) return nullptr;
+ Path removed = p.mid(0, self->path().length());
+ if (removed != self->path())
+ return nullptr;
+ rest = p.dropFront(self->path().length());
+ } else {
+ rest = p;
+ }
+
+ AttachedInfo::Ptr res = self;
+ while (rest) {
+ if (!res)
+ break;
+ res = res->m_subItems.value(rest.head());
+ rest = rest.dropFront();
+ }
+ return res;
+}
+
+AttachedInfoLookupResult<AttachedInfo::Ptr>
+AttachedInfo::findAttachedInfo(const DomItem &item, QStringView fieldName)
+{
+ Path p;
+ DomItem fLoc = item.field(fieldName);
+ if (!fLoc) {
+ // owner or container.owner should be a file, so this works, but we could simply use the
+ // canonical path, and PathType::Canonical instead...
+ DomItem o = item.owner();
+ p = item.pathFromOwner();
+ fLoc = o.field(fieldName);
+ while (!fLoc && o) {
+ DomItem c = o.container();
+ p = c.pathFromOwner().path(o.canonicalPath().last()).path(p);
+ o = c.owner();
+ fLoc = o.field(fieldName);
+ }
+ }
+ AttachedInfoLookupResult<AttachedInfo::Ptr> res;
+ res.lookupPath = p;
+ if (AttachedInfo::Ptr fLocPtr = fLoc.ownerAs<AttachedInfo>())
+ if (AttachedInfo::Ptr foundTree =
+ AttachedInfo::find(fLocPtr, p, AttachedInfo::PathType::Relative))
+ res.foundTree = foundTree;
+ res.rootTreePath = fLoc.canonicalPath();
+
+ res.foundTreePath = res.rootTreePath;
+ for (const Path &pEl : res.lookupPath)
+ res.foundTreePath = res.foundTreePath.field(Fields::subItems).key(pEl.toString());
+ return res;
+}
+
+bool UpdatedScriptExpression::iterateDirectSubpaths(const DomItem &self, DirectVisitor visitor) const
+{
+ bool cont = true;
+ cont = cont && self.dvWrapField(visitor, Fields::expr, expr);
+ return cont;
+}
+
+UpdatedScriptExpression::Tree UpdatedScriptExpression::createTree(const Path &basePath)
+{
+ return AttachedInfoT<UpdatedScriptExpression>::createTree(basePath);
+}
+
+UpdatedScriptExpression::Tree UpdatedScriptExpression::ensure(
+ const UpdatedScriptExpression::Tree &base, const Path &basePath,
+ AttachedInfo::PathType pType)
+{
+ return AttachedInfoT<UpdatedScriptExpression>::ensure(base, basePath, pType);
+}
+
+AttachedInfoLookupResult<UpdatedScriptExpression::Tree>
+UpdatedScriptExpression::findAttachedInfo(const DomItem &item)
+{
+ return AttachedInfoT<UpdatedScriptExpression>::findAttachedInfo(
+ item, Fields::updatedScriptExpressions);
+}
+
+UpdatedScriptExpression::Tree UpdatedScriptExpression::treePtr(const DomItem &item)
+{
+ return AttachedInfoT<UpdatedScriptExpression>::treePtr(item, Fields::updatedScriptExpressions);
+}
+
+const UpdatedScriptExpression *UpdatedScriptExpression::exprPtr(const DomItem &item)
+{
+ if (UpdatedScriptExpression::Tree t = treePtr(item))
+ return &(t->info());
+ return nullptr;
+}
+
+bool UpdatedScriptExpression::visitTree(
+ const Tree &base, function_ref<bool(const Path &, const Tree &)> visitor,
+ const Path &basePath)
+{
+ return AttachedInfoT<UpdatedScriptExpression>::visitTree(base, visitor, basePath);
+}
+
+} // namespace Dom
+} // namespace QQmlJS
+QT_END_NAMESPACE
+
+#include "moc_qqmldomattachedinfo_p.cpp"
diff --git a/src/qmldom/qqmldomattachedinfo_p.h b/src/qmldom/qqmldomattachedinfo_p.h
new file mode 100644
index 0000000000..8412c3ab9b
--- /dev/null
+++ b/src/qmldom/qqmldomattachedinfo_p.h
@@ -0,0 +1,311 @@
+// Copyright (C) 2021 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+
+#ifndef QMLDOMATTACHEDINFO_P_H
+#define QMLDOMATTACHEDINFO_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>
+#include <optional>
+
+QT_BEGIN_NAMESPACE
+
+namespace QQmlJS {
+namespace Dom {
+struct AttachedInfoLookupResultBase
+{
+ Path lookupPath;
+ Path rootTreePath;
+ Path foundTreePath;
+};
+template<typename TreePtr>
+class AttachedInfoLookupResult: public AttachedInfoLookupResultBase
+{
+public:
+ TreePtr foundTree;
+
+ operator bool() { return bool(foundTree); }
+ template<typename T>
+ AttachedInfoLookupResult<std::shared_ptr<T>> as() const
+ {
+ AttachedInfoLookupResult<std::shared_ptr<T>> res;
+ res.AttachedInfoLookupResultBase::operator=(*this);
+ res.foundTree = std::static_pointer_cast<T>(foundTree);
+ return res;
+ }
+};
+
+class QMLDOM_EXPORT AttachedInfo : public OwningItem {
+ Q_GADGET
+public:
+ enum class PathType {
+ Relative,
+ Canonical
+ };
+ Q_ENUM(PathType)
+
+ constexpr static DomType kindValue = DomType::AttachedInfo;
+ using Ptr = std::shared_ptr<AttachedInfo>;
+
+ DomType kind() const override { return kindValue; }
+ Path canonicalPath(const DomItem &self) const override { return self.m_ownerPath; }
+ bool iterateDirectSubpaths(const DomItem &self, DirectVisitor visitor) const override;
+
+ AttachedInfo::Ptr makeCopy(const DomItem &self) const
+ {
+ return std::static_pointer_cast<AttachedInfo>(doCopy(self));
+ }
+
+ Ptr parent() const { return m_parent.lock(); }
+ Path path() const { return m_path; }
+ void setPath(const Path &p) { m_path = p; }
+
+ AttachedInfo(const Ptr &parent = nullptr, const Path &p = Path())
+ : m_path(p), m_parent(parent)
+ {}
+
+ AttachedInfo(const AttachedInfo &o);
+
+ static Ptr ensure(const Ptr &self, const Path &path, PathType pType = PathType::Relative);
+ static Ptr find(const Ptr &self, const Path &p, PathType pType = PathType::Relative);
+ static AttachedInfoLookupResult<Ptr> findAttachedInfo(const DomItem &item,
+ QStringView treeFieldName);
+ static Ptr treePtr(const DomItem &item, QStringView fieldName)
+ {
+ return findAttachedInfo(item, fieldName).foundTree;
+ }
+
+ DomItem itemAtPath(const DomItem &self, const Path &p, PathType pType = PathType::Relative) const
+ {
+ if (Ptr resPtr = find(self.ownerAs<AttachedInfo>(), p, pType)) {
+ const Path relative = (pType == PathType::Canonical) ? p.mid(m_path.length()) : p;
+ Path resPath = self.canonicalPath();
+ for (const Path &pEl : relative) {
+ resPath = resPath.field(Fields::subItems).key(pEl.toString());
+ }
+ return self.copy(resPtr, resPath);
+ }
+ return DomItem();
+ }
+
+ DomItem infoAtPath(const DomItem &self, const Path &p, PathType pType = PathType::Relative) const
+ {
+ return itemAtPath(self, p, pType).field(Fields::infoItem);
+ }
+
+ MutableDomItem ensureItemAtPath(MutableDomItem &self, const Path &p,
+ PathType pType = PathType::Relative)
+ {
+ if (Ptr resPtr = ensure(self.ownerAs<AttachedInfo>(), p, pType)) {
+ const Path relative = (pType == PathType::Canonical) ? p.mid(m_path.length()) : p;
+ Path resPath = self.canonicalPath();
+ for (const Path &pEl : relative) {
+ resPath = resPath.field(Fields::subItems).key(pEl.toString());
+ }
+ return MutableDomItem(self.item().copy(resPtr, resPath));
+ }
+ return MutableDomItem();
+ }
+
+ MutableDomItem ensureInfoAtPath(MutableDomItem &self, const Path &p,
+ PathType pType = PathType::Relative)
+ {
+ return ensureItemAtPath(self, p, pType).field(Fields::infoItem);
+ }
+
+ virtual AttachedInfo::Ptr instantiate(
+ const AttachedInfo::Ptr &parent, const Path &p = Path()) const = 0;
+ virtual DomItem infoItem(const DomItem &self) const = 0;
+ QMap<Path, Ptr> subItems() const {
+ return m_subItems;
+ }
+ void setSubItems(QMap<Path, Ptr> v) {
+ m_subItems = v;
+ }
+protected:
+ Path m_path;
+ std::weak_ptr<AttachedInfo> m_parent;
+ QMap<Path, Ptr> m_subItems;
+};
+
+template<typename Info>
+class QMLDOM_EXPORT AttachedInfoT final : public AttachedInfo
+{
+public:
+ constexpr static DomType kindValue = DomType::AttachedInfo;
+ using Ptr = std::shared_ptr<AttachedInfoT>;
+ using InfoType = Info;
+
+ AttachedInfoT(const Ptr &parent = nullptr, const Path &p = Path()) : AttachedInfo(parent, p) {}
+ AttachedInfoT(const AttachedInfoT &o):
+ AttachedInfo(o),
+ m_info(o.m_info)
+ {
+ auto end = o.m_subItems.end();
+ auto i = o.m_subItems.begin();
+ while (i != end) {
+ m_subItems.insert(i.key(), Ptr(
+ new AttachedInfoT(*std::static_pointer_cast<AttachedInfoT>(i.value()).get())));
+ }
+ }
+
+ static Ptr createTree(const Path &p = Path()) {
+ return Ptr(new AttachedInfoT(nullptr, p));
+ }
+
+ static Ptr ensure(const Ptr &self, const Path &path, PathType pType = PathType::Relative)
+ {
+ return std::static_pointer_cast<AttachedInfoT>(AttachedInfo::ensure(self, path, pType));
+ }
+
+ static Ptr find(const Ptr &self, const Path &p, PathType pType = PathType::Relative)
+ {
+ return std::static_pointer_cast<AttachedInfoT>(AttachedInfo::find(self, p, pType));
+ }
+
+ static AttachedInfoLookupResult<Ptr> findAttachedInfo(const DomItem &item,
+ QStringView fieldName)
+ {
+ return AttachedInfo::findAttachedInfo(item, fieldName).template as<AttachedInfoT>();
+ }
+ static Ptr treePtr(const DomItem &item, QStringView fieldName)
+ {
+ return std::static_pointer_cast<AttachedInfoT>(AttachedInfo::treePtr(item, fieldName));
+ }
+ static bool visitTree(
+ const Ptr &base, function_ref<bool(const Path &, const Ptr &)> visitor,
+ const Path &basePath = Path()) {
+ if (base) {
+ Path pNow = basePath.path(base->path());
+ if (visitor(pNow, base)) {
+ auto it = base->m_subItems.cbegin();
+ auto end = base->m_subItems.cend();
+ while (it != end) {
+ if (!visitTree(std::static_pointer_cast<AttachedInfoT>(it.value()), visitor, pNow))
+ return false;
+ ++it;
+ }
+ } else {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ AttachedInfo::Ptr instantiate(
+ const AttachedInfo::Ptr &parent, const Path &p = Path()) const override
+ {
+ return Ptr(new AttachedInfoT(std::static_pointer_cast<AttachedInfoT>(parent), p));
+ }
+
+ DomItem infoItem(const DomItem &self) const override { return self.wrapField(Fields::infoItem, m_info); }
+
+ Ptr makeCopy(const DomItem &self) const
+ {
+ return std::static_pointer_cast<AttachedInfoT>(doCopy(self));
+ }
+
+ Ptr parent() const { return std::static_pointer_cast<AttachedInfoT>(AttachedInfo::parent()); }
+
+ const Info &info() const { return m_info; }
+ Info &info() { return m_info; }
+
+ QString canonicalPathForTesting() const
+ {
+ QString result;
+ for (auto *it = this; it; it = it->parent().get()) {
+ result.prepend(it->path().toString());
+ }
+ return result;
+ }
+
+protected:
+ std::shared_ptr<OwningItem> doCopy(const DomItem &) const override
+ {
+ return Ptr(new AttachedInfoT(*this));
+ }
+
+private:
+ Info m_info;
+};
+
+class QMLDOM_EXPORT FileLocations {
+public:
+ using Tree = std::shared_ptr<AttachedInfoT<FileLocations>>;
+ constexpr static DomType kindValue = DomType::FileLocations;
+ DomType kind() const { return kindValue; }
+ bool iterateDirectSubpaths(const DomItem &self, DirectVisitor) const;
+
+ static Tree createTree(const Path &basePath);
+ static Tree ensure(const Tree &base, const Path &basePath,
+ AttachedInfo::PathType pType = AttachedInfo::PathType::Relative);
+ static Tree find(const Tree &self, const Path &p,
+ AttachedInfo::PathType pType = AttachedInfo::PathType::Relative)
+ {
+ return AttachedInfoT<FileLocations>::find(self, p, pType);
+ }
+
+ // returns the path looked up and the found tree when looking for the info attached to item
+ static AttachedInfoLookupResult<Tree> findAttachedInfo(const DomItem &item);
+ static FileLocations::Tree treeOf(const DomItem &);
+ static const FileLocations *fileLocationsOf(const DomItem &);
+
+ static void updateFullLocation(const Tree &fLoc, SourceLocation loc);
+ static void addRegion(const Tree &fLoc, FileLocationRegion region, SourceLocation loc);
+ static QQmlJS::SourceLocation region(const Tree &fLoc, FileLocationRegion region);
+
+private:
+ static QMetaEnum regionEnum;
+
+public:
+ SourceLocation fullRegion;
+ QMap<FileLocationRegion, SourceLocation> regions;
+ QMap<FileLocationRegion, QList<SourceLocation>> preCommentLocations;
+ QMap<FileLocationRegion, QList<SourceLocation>> postCommentLocations;
+};
+
+class QMLDOM_EXPORT UpdatedScriptExpression
+{
+ Q_GADGET
+public:
+ using Tree = std::shared_ptr<AttachedInfoT<UpdatedScriptExpression>>;
+ constexpr static DomType kindValue = DomType::UpdatedScriptExpression;
+ DomType kind() const { return kindValue; }
+ bool iterateDirectSubpaths(const DomItem &self, DirectVisitor) const;
+
+ static Tree createTree(const Path &basePath);
+ static Tree ensure(const Tree &base, const Path &basePath, AttachedInfo::PathType pType);
+
+ // returns the path looked up and the found tree when looking for the info attached to item
+ static AttachedInfoLookupResult<Tree>
+ findAttachedInfo(const DomItem &item);
+ // convenience: find FileLocations::Tree attached to the given item
+ static Tree treePtr(const DomItem &);
+ // convenience: find FileLocations* attached to the given item (if there is one)
+ static const UpdatedScriptExpression *exprPtr(const DomItem &);
+
+ static bool visitTree(
+ const Tree &base, function_ref<bool(const Path &, const Tree &)> visitor,
+ const Path &basePath = Path());
+
+ std::shared_ptr<ScriptExpression> expr;
+};
+
+} // end namespace Dom
+} // end namespace QQmlJS
+
+QT_END_NAMESPACE
+#endif // QMLDOMATTACHEDINFO_P_H
diff --git a/src/qmldom/qqmldomcodeformatter.cpp b/src/qmldom/qqmldomcodeformatter.cpp
new file mode 100644
index 0000000000..5660025fd8
--- /dev/null
+++ b/src/qmldom/qqmldomcodeformatter.cpp
@@ -0,0 +1,1409 @@
+// Copyright (C) 2022 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+
+#include "qqmldomcodeformatter_p.h"
+
+#include <QLoggingCategory>
+#include <QMetaEnum>
+
+static Q_LOGGING_CATEGORY(formatterLog, "qt.qmldom.formatter", QtWarningMsg);
+
+QT_BEGIN_NAMESPACE
+namespace QQmlJS {
+namespace Dom {
+
+using StateType = FormatTextStatus::StateType;
+using State = FormatTextStatus::State;
+
+State FormatTextStatus::state(int belowTop) const
+{
+ if (belowTop < states.size())
+ return states.at(states.size() - 1 - belowTop);
+ else
+ return State();
+}
+
+QString FormatTextStatus::stateToString(StateType type)
+{
+ const QMetaEnum &metaEnum =
+ staticMetaObject.enumerator(staticMetaObject.indexOfEnumerator("StateType"));
+ return QString::fromUtf8(metaEnum.valueToKey(int(type)));
+}
+
+void FormatPartialStatus::enterState(StateType newState)
+{
+ int savedIndentDepth = currentIndent;
+ defaultOnEnter(newState, &currentIndent, &savedIndentDepth);
+ currentStatus.pushState(newState, savedIndentDepth);
+ qCDebug(formatterLog) << "enter state" << FormatTextStatus::stateToString(newState);
+
+ if (newState == StateType::BracketOpen)
+ enterState(StateType::BracketElementStart);
+}
+
+void FormatPartialStatus::leaveState(bool statementDone)
+{
+ Q_ASSERT(currentStatus.size() > 1);
+ if (currentStatus.state().type == StateType::TopmostIntro)
+ return;
+
+ // restore indent depth
+ State poppedState = currentStatus.popState();
+ currentIndent = poppedState.savedIndentDepth;
+
+ StateType topState = currentStatus.state().type;
+
+ qCDebug(formatterLog) << "left state" << FormatTextStatus::stateToString(poppedState.type)
+ << ", now in state" << FormatTextStatus::stateToString(topState);
+
+ // if statement is done, may need to leave recursively
+ if (statementDone) {
+ if (topState == StateType::IfStatement) {
+ if (poppedState.type != StateType::MaybeElse)
+ enterState(StateType::MaybeElse);
+ else
+ leaveState(true);
+ } else if (topState == StateType::ElseClause) {
+ // leave the else *and* the surrounding if, to prevent another else
+ leaveState(false);
+ leaveState(true);
+ } else if (topState == StateType::TryStatement) {
+ if (poppedState.type != StateType::MaybeCatchOrFinally
+ && poppedState.type != StateType::FinallyStatement) {
+ enterState(StateType::MaybeCatchOrFinally);
+ } else {
+ leaveState(true);
+ }
+ } else if (!FormatTextStatus::isExpressionEndState(topState)) {
+ leaveState(true);
+ }
+ }
+}
+
+void FormatPartialStatus::turnIntoState(StateType newState)
+{
+ leaveState(false);
+ enterState(newState);
+}
+
+const Token &FormatPartialStatus::tokenAt(int idx) const
+{
+ static const Token empty;
+ if (idx < 0 || idx >= lineTokens.size())
+ return empty;
+ else
+ return lineTokens.at(idx);
+}
+
+int FormatPartialStatus::column(int index) const
+{
+ if (index > line.size())
+ index = line.size();
+ IndentInfo indent(QStringView(line).mid(0, index), options.tabSize, indentOffset);
+ return indent.column;
+}
+
+QStringView FormatPartialStatus::tokenText(const Token &token) const
+{
+ return line.mid(token.begin(), token.length);
+}
+
+bool FormatPartialStatus::tryInsideExpression(bool alsoExpression)
+{
+ StateType newState = StateType::Invalid;
+ const int kind = tokenAt(tokenIndex).lexKind;
+ switch (kind) {
+ case QQmlJSGrammar::T_LPAREN:
+ newState = StateType::ParenOpen;
+ break;
+ case QQmlJSGrammar::T_LBRACKET:
+ newState = StateType::BracketOpen;
+ break;
+ case QQmlJSGrammar::T_LBRACE:
+ newState = StateType::ObjectliteralOpen;
+ break;
+ case QQmlJSGrammar::T_FUNCTION:
+ newState = StateType::FunctionStart;
+ break;
+ case QQmlJSGrammar::T_QUESTION:
+ newState = StateType::TernaryOp;
+ break;
+ }
+
+ if (newState != StateType::Invalid) {
+ if (alsoExpression)
+ enterState(StateType::Expression);
+ enterState(newState);
+ return true;
+ }
+
+ return false;
+}
+
+bool FormatPartialStatus::tryStatement()
+{
+ Token t = tokenAt(tokenIndex);
+ const int kind = t.lexKind;
+ switch (kind) {
+ case QQmlJSGrammar::T_AUTOMATIC_SEMICOLON:
+ case QQmlJSGrammar::T_COMPATIBILITY_SEMICOLON:
+ case QQmlJSGrammar::T_SEMICOLON:
+ enterState(StateType::EmptyStatement);
+ leaveState(true);
+ return true;
+ case QQmlJSGrammar::T_BREAK:
+ case QQmlJSGrammar::T_CONTINUE:
+ enterState(StateType::BreakcontinueStatement);
+ return true;
+ case QQmlJSGrammar::T_THROW:
+ enterState(StateType::ThrowStatement);
+ enterState(StateType::Expression);
+ return true;
+ case QQmlJSGrammar::T_RETURN:
+ enterState(StateType::ReturnStatement);
+ enterState(StateType::Expression);
+ return true;
+ case QQmlJSGrammar::T_WHILE:
+ case QQmlJSGrammar::T_FOR:
+ case QQmlJSGrammar::T_CATCH:
+ enterState(StateType::StatementWithCondition);
+ return true;
+ case QQmlJSGrammar::T_SWITCH:
+ enterState(StateType::SwitchStatement);
+ return true;
+ case QQmlJSGrammar::T_IF:
+ enterState(StateType::IfStatement);
+ return true;
+ case QQmlJSGrammar::T_DO:
+ enterState(StateType::DoStatement);
+ enterState(StateType::Substatement);
+ return true;
+ case QQmlJSGrammar::T_CASE:
+ case QQmlJSGrammar::T_DEFAULT:
+ enterState(StateType::CaseStart);
+ return true;
+ case QQmlJSGrammar::T_TRY:
+ enterState(StateType::TryStatement);
+ return true;
+ case QQmlJSGrammar::T_LBRACE:
+ enterState(StateType::JsblockOpen);
+ return true;
+ case QQmlJSGrammar::T_VAR:
+ case QQmlJSGrammar::T_PLUS_PLUS:
+ case QQmlJSGrammar::T_MINUS_MINUS:
+ case QQmlJSGrammar::T_IMPORT:
+ case QQmlJSGrammar::T_SIGNAL:
+ case QQmlJSGrammar::T_ON:
+ case QQmlJSGrammar::T_AS:
+ case QQmlJSGrammar::T_PROPERTY:
+ case QQmlJSGrammar::T_REQUIRED:
+ case QQmlJSGrammar::T_READONLY:
+ case QQmlJSGrammar::T_FUNCTION:
+ case QQmlJSGrammar::T_FUNCTION_STAR:
+ case QQmlJSGrammar::T_NUMERIC_LITERAL:
+ case QQmlJSGrammar::T_LPAREN:
+ enterState(StateType::Expression);
+ // look at the token again
+ tokenIndex -= 1;
+ return true;
+ default:
+ if (Token::lexKindIsIdentifier(kind)) {
+ enterState(StateType::ExpressionOrLabel);
+ return true;
+ } else if (Token::lexKindIsDelimiter(kind) || Token::lexKindIsStringType(kind)) {
+ enterState(StateType::Expression);
+ // look at the token again
+ tokenIndex -= 1;
+ return true;
+ }
+ }
+ return false;
+}
+
+void FormatPartialStatus::dump() const
+{
+ qCDebug(formatterLog) << "Current token index" << tokenIndex;
+ qCDebug(formatterLog) << "Current state:";
+ for (const State &s : currentStatus.states)
+ qCDebug(formatterLog) << FormatTextStatus::stateToString(s.type) << s.savedIndentDepth;
+ qCDebug(formatterLog) << "Current lexerState:" << currentStatus.lexerState.state;
+ qCDebug(formatterLog) << "Current indent:" << currentIndent;
+}
+
+void FormatPartialStatus::handleTokens()
+{
+ auto enter = [this](StateType newState) { this->enterState(newState); };
+
+ auto leave = [this](bool statementDone = false) { this->leaveState(statementDone); };
+
+ auto turnInto = [this](StateType newState) { this->turnIntoState(newState); };
+
+ qCDebug(formatterLog) << "Starting to look at " << line;
+
+ for (; tokenIndex < lineTokens.size();) {
+ Token currentToken = tokenAt(tokenIndex);
+ const int kind = currentToken.lexKind;
+
+ qCDebug(formatterLog) << "Token: " << tokenText(currentToken);
+
+ if (Token::lexKindIsComment(kind)
+ && currentStatus.state().type != StateType::MultilineCommentCont
+ && currentStatus.state().type != StateType::MultilineCommentStart) {
+ tokenIndex += 1;
+ continue;
+ }
+
+ switch (currentStatus.state().type) {
+ case StateType::TopmostIntro:
+ switch (kind) {
+ case QQmlJSGrammar::T_IDENTIFIER:
+ enter(StateType::ObjectdefinitionOrJs);
+ continue;
+ case QQmlJSGrammar::T_IMPORT:
+ enter(StateType::TopQml);
+ continue;
+ case QQmlJSGrammar::T_LBRACE:
+ enter(StateType::TopJs);
+ enter(StateType::Expression);
+ continue; // if a file starts with {, it's likely json
+ default:
+ enter(StateType::TopJs);
+ continue;
+ }
+ break;
+
+ case StateType::TopQml:
+ switch (kind) {
+ case QQmlJSGrammar::T_IMPORT:
+ enter(StateType::ImportStart);
+ break;
+ case QQmlJSGrammar::T_IDENTIFIER:
+ enter(StateType::BindingOrObjectdefinition);
+ break;
+ default:
+ if (Token::lexKindIsIdentifier(kind))
+ enter(StateType::BindingOrObjectdefinition);
+ break;
+ }
+ break;
+
+ case StateType::TopJs:
+ tryStatement();
+ break;
+
+ case StateType::ObjectdefinitionOrJs:
+ switch (kind) {
+ case QQmlJSGrammar::T_DOT:
+ break;
+ case QQmlJSGrammar::T_LBRACE:
+ turnInto(StateType::BindingOrObjectdefinition);
+ continue;
+ default:
+ if (!Token::lexKindIsIdentifier(kind) || !line.at(currentToken.begin()).isUpper()) {
+ turnInto(StateType::TopJs);
+ continue;
+ }
+ }
+ break;
+
+ case StateType::ImportStart:
+ enter(StateType::ImportMaybeDotOrVersionOrAs);
+ break;
+
+ case StateType::ImportMaybeDotOrVersionOrAs:
+ switch (kind) {
+ case QQmlJSGrammar::T_DOT:
+ turnInto(StateType::ImportDot);
+ break;
+ case QQmlJSGrammar::T_AS:
+ turnInto(StateType::ImportAs);
+ break;
+ case QQmlJSGrammar::T_NUMERIC_LITERAL:
+ case QQmlJSGrammar::T_VERSION_NUMBER:
+ turnInto(StateType::ImportMaybeAs);
+ break;
+ default:
+ leave();
+ leave();
+ continue;
+ }
+ break;
+
+ case StateType::ImportMaybeAs:
+ switch (kind) {
+ case QQmlJSGrammar::T_AS:
+ turnInto(StateType::ImportAs);
+ break;
+ default:
+ leave();
+ leave();
+ continue;
+ }
+ break;
+
+ case StateType::ImportDot:
+ if (Token::lexKindIsIdentifier(kind)) {
+ turnInto(StateType::ImportMaybeDotOrVersionOrAs);
+ } else {
+ leave();
+ leave();
+ continue;
+ }
+ break;
+
+ case StateType::ImportAs:
+ if (Token::lexKindIsIdentifier(kind)) {
+ leave();
+ leave();
+ }
+ break;
+
+ case StateType::BindingOrObjectdefinition:
+ switch (kind) {
+ case QQmlJSGrammar::T_COLON:
+ enter(StateType::BindingAssignment);
+ break;
+ case QQmlJSGrammar::T_LBRACE:
+ enter(StateType::ObjectdefinitionOpen);
+ break;
+ }
+ break;
+
+ case StateType::BindingAssignment:
+ switch (kind) {
+ case QQmlJSGrammar::T_AUTOMATIC_SEMICOLON:
+ case QQmlJSGrammar::T_COMPATIBILITY_SEMICOLON:
+ case QQmlJSGrammar::T_SEMICOLON:
+ leave(true);
+ break;
+ case QQmlJSGrammar::T_IF:
+ enter(StateType::IfStatement);
+ break;
+ case QQmlJSGrammar::T_WITH:
+ enter(StateType::StatementWithCondition);
+ break;
+ case QQmlJSGrammar::T_TRY:
+ enter(StateType::TryStatement);
+ break;
+ case QQmlJSGrammar::T_SWITCH:
+ enter(StateType::SwitchStatement);
+ break;
+ case QQmlJSGrammar::T_LBRACE:
+ enter(StateType::JsblockOpen);
+ break;
+ case QQmlJSGrammar::T_ON:
+ case QQmlJSGrammar::T_AS:
+ case QQmlJSGrammar::T_IMPORT:
+ case QQmlJSGrammar::T_SIGNAL:
+ case QQmlJSGrammar::T_PROPERTY:
+ case QQmlJSGrammar::T_REQUIRED:
+ case QQmlJSGrammar::T_READONLY:
+ case QQmlJSGrammar::T_IDENTIFIER:
+ enter(StateType::ExpressionOrObjectdefinition);
+ break;
+
+ // error recovery
+ case QQmlJSGrammar::T_RBRACKET:
+ case QQmlJSGrammar::T_RPAREN:
+ leave(true);
+ break;
+
+ default:
+ enter(StateType::Expression);
+ continue;
+ }
+ break;
+
+ case StateType::ObjectdefinitionOpen:
+ switch (kind) {
+ case QQmlJSGrammar::T_RBRACE:
+ leave(true);
+ break;
+ case QQmlJSGrammar::T_DEFAULT:
+ case QQmlJSGrammar::T_READONLY:
+ enter(StateType::PropertyModifiers);
+ break;
+ case QQmlJSGrammar::T_PROPERTY:
+ enter(StateType::PropertyStart);
+ break;
+ case QQmlJSGrammar::T_REQUIRED:
+ enter(StateType::RequiredProperty);
+ break;
+ case QQmlJSGrammar::T_COMPONENT:
+ enter(StateType::ComponentStart);
+ break;
+ case QQmlJSGrammar::T_FUNCTION:
+ case QQmlJSGrammar::T_FUNCTION_STAR:
+ enter(StateType::FunctionStart);
+ break;
+ case QQmlJSGrammar::T_SIGNAL:
+ enter(StateType::SignalStart);
+ break;
+ case QQmlJSGrammar::T_ENUM:
+ enter(StateType::EnumStart);
+ break;
+ case QQmlJSGrammar::T_ON:
+ case QQmlJSGrammar::T_AS:
+ case QQmlJSGrammar::T_IMPORT:
+ enter(StateType::BindingOrObjectdefinition);
+ break;
+ default:
+ if (Token::lexKindIsIdentifier(kind))
+ enter(StateType::BindingOrObjectdefinition);
+ break;
+ }
+ break;
+
+ case StateType::PropertyModifiers:
+ switch (kind) {
+ case QQmlJSGrammar::T_PROPERTY:
+ turnInto(StateType::PropertyStart);
+ break;
+ case QQmlJSGrammar::T_DEFAULT:
+ case QQmlJSGrammar::T_READONLY:
+ break;
+ case QQmlJSGrammar::T_REQUIRED:
+ turnInto(StateType::RequiredProperty);
+ break;
+ default:
+ leave(true);
+ break;
+ }
+ break;
+
+ case StateType::PropertyStart:
+ switch (kind) {
+ case QQmlJSGrammar::T_COLON:
+ enter(StateType::BindingAssignment);
+ break; // oops, was a binding
+ case QQmlJSGrammar::T_VAR:
+ case QQmlJSGrammar::T_IDENTIFIER:
+ enter(StateType::PropertyName);
+ break;
+ default:
+ if (Token::lexKindIsIdentifier(kind) && tokenText(currentToken) == u"list") {
+ enter(StateType::PropertyListOpen);
+ } else {
+ leave(true);
+ continue;
+ }
+ }
+ break;
+
+ case StateType::RequiredProperty:
+ switch (kind) {
+ case QQmlJSGrammar::T_PROPERTY:
+ turnInto(StateType::PropertyStart);
+ break;
+ case QQmlJSGrammar::T_DEFAULT:
+ case QQmlJSGrammar::T_READONLY:
+ turnInto(StateType::PropertyModifiers);
+ break;
+ case QQmlJSGrammar::T_IDENTIFIER:
+ leave(true);
+ break;
+ default:
+ leave(true);
+ continue;
+ }
+ break;
+
+ case StateType::ComponentStart:
+ switch (kind) {
+ case QQmlJSGrammar::T_IDENTIFIER:
+ turnInto(StateType::ComponentName);
+ break;
+ default:
+ leave(true);
+ continue;
+ }
+ break;
+
+ case StateType::ComponentName:
+ switch (kind) {
+ case QQmlJSGrammar::T_COLON:
+ enter(StateType::BindingAssignment);
+ break;
+ default:
+ leave(true);
+ continue;
+ }
+ break;
+
+ case StateType::PropertyName:
+ turnInto(StateType::PropertyMaybeInitializer);
+ break;
+
+ case StateType::PropertyListOpen: {
+ const QStringView tok = tokenText(currentToken);
+ if (tok == u">")
+ turnInto(StateType::PropertyName);
+ break;
+ }
+ case StateType::PropertyMaybeInitializer:
+ switch (kind) {
+ case QQmlJSGrammar::T_COLON:
+ turnInto(StateType::BindingAssignment);
+ break;
+ default:
+ leave(true);
+ continue;
+ }
+ break;
+
+ case StateType::EnumStart:
+ switch (kind) {
+ case QQmlJSGrammar::T_LBRACE:
+ enter(StateType::ObjectliteralOpen);
+ break;
+ }
+ break;
+
+ case StateType::SignalStart:
+ switch (kind) {
+ case QQmlJSGrammar::T_COLON:
+ enter(StateType::BindingAssignment);
+ break; // oops, was a binding
+ default:
+ enter(StateType::SignalMaybeArglist);
+ break;
+ }
+ break;
+
+ case StateType::SignalMaybeArglist:
+ switch (kind) {
+ case QQmlJSGrammar::T_LPAREN:
+ turnInto(StateType::SignalArglistOpen);
+ break;
+ default:
+ leave(true);
+ continue;
+ }
+ break;
+
+ case StateType::SignalArglistOpen:
+ switch (kind) {
+ case QQmlJSGrammar::T_RPAREN:
+ leave(true);
+ break;
+ }
+ break;
+
+ case StateType::FunctionStart:
+ switch (kind) {
+ case QQmlJSGrammar::T_LPAREN:
+ enter(StateType::FunctionArglistOpen);
+ break;
+ }
+ break;
+
+ case StateType::FunctionArglistOpen:
+ switch (kind) {
+ case QQmlJSGrammar::T_COLON:
+ enter(StateType::TypeAnnotation);
+ break;
+ case QQmlJSGrammar::T_RPAREN:
+ turnInto(StateType::FunctionArglistClosed);
+ break;
+ }
+ break;
+
+ case StateType::FunctionArglistClosed:
+ switch (kind) {
+ case QQmlJSGrammar::T_COLON:
+ enter(StateType::TypeAnnotation);
+ break;
+ case QQmlJSGrammar::T_LBRACE:
+ turnInto(StateType::JsblockOpen);
+ break;
+ default:
+ leave(true);
+ continue; // error recovery
+ }
+ break;
+
+ case StateType::TypeAnnotation:
+ switch (kind) {
+ case QQmlJSGrammar::T_IDENTIFIER:
+ case QQmlJSGrammar::T_DOT:
+ break;
+ case QQmlJSGrammar::T_LT:
+ turnInto(StateType::TypeParameter);
+ break;
+ default:
+ leave();
+ continue; // error recovery
+ }
+ break;
+
+ case StateType::TypeParameter:
+ switch (kind) {
+ case QQmlJSGrammar::T_LT:
+ enter(StateType::TypeParameter);
+ break;
+ case QQmlJSGrammar::T_GT:
+ leave();
+ break;
+ }
+ break;
+
+ case StateType::ExpressionOrObjectdefinition:
+ switch (kind) {
+ case QQmlJSGrammar::T_DOT:
+ break; // need to become an objectdefinition_open in cases like "width: Qt.Foo
+ // {"
+ case QQmlJSGrammar::T_LBRACE:
+ turnInto(StateType::ObjectdefinitionOpen);
+ break;
+
+ // propagate 'leave' from expression state
+ case QQmlJSGrammar::T_RBRACKET:
+ case QQmlJSGrammar::T_RPAREN:
+ leave();
+ continue;
+
+ default:
+ if (Token::lexKindIsIdentifier(kind))
+ break; // need to become an objectdefinition_open in cases like "width:
+ // Qt.Foo
+ enter(StateType::Expression);
+ continue; // really? identifier and more tokens might already be gone
+ }
+ break;
+
+ case StateType::ExpressionOrLabel:
+ switch (kind) {
+ case QQmlJSGrammar::T_COLON:
+ turnInto(StateType::LabelledStatement);
+ break;
+
+ // propagate 'leave' from expression state
+ case QQmlJSGrammar::T_RBRACKET:
+ case QQmlJSGrammar::T_RPAREN:
+ leave();
+ continue;
+
+ default:
+ enter(StateType::Expression);
+ continue;
+ }
+ break;
+
+ case StateType::TernaryOp:
+ if (kind == QQmlJSGrammar::T_COLON) {
+ enter(StateType::TernaryOpAfterColon);
+ enter(StateType::ExpressionContinuation);
+ break;
+ }
+ Q_FALLTHROUGH();
+ case StateType::TernaryOpAfterColon:
+ case StateType::Expression:
+ if (tryInsideExpression(false))
+ break;
+ switch (kind) {
+ case QQmlJSGrammar::T_COMMA:
+ leave(true);
+ break;
+ case QQmlJSGrammar::T_RBRACKET:
+ case QQmlJSGrammar::T_RPAREN:
+ leave();
+ continue;
+ case QQmlJSGrammar::T_RBRACE:
+ leave(true);
+ continue;
+ case QQmlJSGrammar::T_AUTOMATIC_SEMICOLON:
+ case QQmlJSGrammar::T_COMPATIBILITY_SEMICOLON:
+ case QQmlJSGrammar::T_SEMICOLON:
+ leave(true);
+ break;
+ default:
+ if (Token::lexKindIsDelimiter(kind))
+ enter(StateType::ExpressionContinuation);
+ break;
+ }
+ break;
+
+ case StateType::ExpressionContinuation:
+ leave();
+ continue;
+
+ case StateType::ExpressionMaybeContinuation:
+ switch (kind) {
+ case QQmlJSGrammar::T_QUESTION:
+ case QQmlJSGrammar::T_LBRACKET:
+ case QQmlJSGrammar::T_LPAREN:
+ case QQmlJSGrammar::T_LBRACE:
+ leave();
+ continue;
+ default:
+ leave(!Token::lexKindIsDelimiter(kind));
+ continue;
+ }
+ break;
+
+ case StateType::ParenOpen:
+ if (tryInsideExpression(false))
+ break;
+ switch (kind) {
+ case QQmlJSGrammar::T_RPAREN:
+ leave();
+ break;
+ }
+ break;
+
+ case StateType::BracketOpen:
+ if (tryInsideExpression(false))
+ break;
+ switch (kind) {
+ case QQmlJSGrammar::T_COMMA:
+ enter(StateType::BracketElementStart);
+ break;
+ case QQmlJSGrammar::T_RBRACKET:
+ leave();
+ break;
+ }
+ break;
+
+ case StateType::ObjectliteralOpen:
+ if (tryInsideExpression(false))
+ break;
+ switch (kind) {
+ case QQmlJSGrammar::T_COLON:
+ enter(StateType::ObjectliteralAssignment);
+ break;
+ case QQmlJSGrammar::T_RBRACKET:
+ case QQmlJSGrammar::T_RPAREN:
+ leave();
+ continue; // error recovery
+ case QQmlJSGrammar::T_RBRACE:
+ leave(true);
+ break;
+ }
+ break;
+
+ // pretty much like expression, but ends with , or }
+ case StateType::ObjectliteralAssignment:
+ if (tryInsideExpression(false))
+ break;
+ switch (kind) {
+ case QQmlJSGrammar::T_COMMA:
+ leave();
+ break;
+ case QQmlJSGrammar::T_RBRACKET:
+ case QQmlJSGrammar::T_RPAREN:
+ leave();
+ continue; // error recovery
+ case QQmlJSGrammar::T_RBRACE:
+ leave();
+ continue; // so we also leave objectliteral_open
+ default:
+ if (Token::lexKindIsDelimiter(kind))
+ enter(StateType::ExpressionContinuation);
+ break;
+ }
+ break;
+
+ case StateType::BracketElementStart:
+ if (Token::lexKindIsIdentifier(kind)) {
+ turnInto(StateType::BracketElementMaybeObjectdefinition);
+ } else {
+ leave();
+ continue;
+ }
+ break;
+
+ case StateType::BracketElementMaybeObjectdefinition:
+ switch (kind) {
+ case QQmlJSGrammar::T_LBRACE:
+ turnInto(StateType::ObjectdefinitionOpen);
+ break;
+ default:
+ leave();
+ continue;
+ }
+ break;
+
+ case StateType::JsblockOpen:
+ case StateType::SubstatementOpen:
+ if (tryStatement())
+ break;
+ switch (kind) {
+ case QQmlJSGrammar::T_RBRACE:
+ leave(true);
+ break;
+ }
+ break;
+
+ case StateType::LabelledStatement:
+ if (tryStatement())
+ break;
+ leave(true); // error recovery
+ break;
+
+ case StateType::Substatement:
+ // prefer substatement_open over block_open
+ if (kind != QQmlJSGrammar::T_LBRACE) {
+ if (tryStatement())
+ break;
+ }
+ switch (kind) {
+ case QQmlJSGrammar::T_LBRACE:
+ turnInto(StateType::SubstatementOpen);
+ break;
+ }
+ break;
+
+ case StateType::IfStatement:
+ switch (kind) {
+ case QQmlJSGrammar::T_LPAREN:
+ enter(StateType::ConditionOpen);
+ break;
+ default:
+ leave(true);
+ break; // error recovery
+ }
+ break;
+
+ case StateType::MaybeElse:
+ switch (kind) {
+ case QQmlJSGrammar::T_ELSE:
+ turnInto(StateType::ElseClause);
+ enter(StateType::Substatement);
+ break;
+ default:
+ leave(true);
+ continue;
+ }
+ break;
+
+ case StateType::MaybeCatchOrFinally:
+ switch (kind) {
+ case QQmlJSGrammar::T_CATCH:
+ turnInto(StateType::CatchStatement);
+ break;
+ case QQmlJSGrammar::T_FINALLY:
+ turnInto(StateType::FinallyStatement);
+ break;
+ default:
+ leave(true);
+ continue;
+ }
+ break;
+
+ case StateType::ElseClause:
+ // ### shouldn't happen
+ dump();
+ Q_ASSERT(false);
+ leave(true);
+ break;
+
+ case StateType::ConditionOpen:
+ if (tryInsideExpression(false))
+ break;
+ switch (kind) {
+ case QQmlJSGrammar::T_RPAREN:
+ turnInto(StateType::Substatement);
+ break;
+ }
+ break;
+
+ case StateType::SwitchStatement:
+ case StateType::CatchStatement:
+ case StateType::StatementWithCondition:
+ switch (kind) {
+ case QQmlJSGrammar::T_LPAREN:
+ enter(StateType::StatementWithConditionParenOpen);
+ break;
+ default:
+ leave(true);
+ }
+ break;
+
+ case StateType::StatementWithConditionParenOpen:
+ if (tryInsideExpression(false))
+ break;
+ switch (kind) {
+ case QQmlJSGrammar::T_RPAREN:
+ turnInto(StateType::Substatement);
+ break;
+ }
+ break;
+
+ case StateType::TryStatement:
+ case StateType::FinallyStatement:
+ switch (kind) {
+ case QQmlJSGrammar::T_LBRACE:
+ enter(StateType::JsblockOpen);
+ break;
+ default:
+ leave(true);
+ break;
+ }
+ break;
+
+ case StateType::DoStatement:
+ switch (kind) {
+ case QQmlJSGrammar::T_WHILE:
+ break;
+ case QQmlJSGrammar::T_LPAREN:
+ enter(StateType::DoStatementWhileParenOpen);
+ break;
+ default:
+ leave(true);
+ continue; // error recovery
+ }
+ break;
+
+ case StateType::DoStatementWhileParenOpen:
+ if (tryInsideExpression(false))
+ break;
+ switch (kind) {
+ case QQmlJSGrammar::T_RPAREN:
+ leave();
+ leave(true);
+ break;
+ }
+ break;
+
+ case StateType::BreakcontinueStatement:
+ if (Token ::lexKindIsIdentifier(kind)) {
+ leave(true);
+ } else {
+ leave(true);
+ continue; // try again
+ }
+ break;
+
+ case StateType::CaseStart:
+ switch (kind) {
+ case QQmlJSGrammar::T_COLON:
+ turnInto(StateType::CaseCont);
+ break;
+ }
+ break;
+
+ case StateType::CaseCont:
+ if (kind != QQmlJSGrammar::T_CASE && kind != QQmlJSGrammar::T_DEFAULT && tryStatement())
+ break;
+ switch (kind) {
+ case QQmlJSGrammar::T_RBRACE:
+ leave();
+ continue;
+ case QQmlJSGrammar::T_DEFAULT:
+ case QQmlJSGrammar::T_CASE:
+ leave();
+ continue;
+ }
+ break;
+
+ case StateType::MultilineCommentStart:
+ case StateType::MultilineCommentCont:
+ if (!Token::lexKindIsComment(kind)) {
+ leave();
+ continue;
+ } else if (tokenIndex == lineTokens.size() - 1
+ && !currentStatus.lexerState.isMultiline()) {
+ leave();
+ } else if (tokenIndex == 0) {
+ // to allow enter/leave to update the indentDepth
+ turnInto(StateType::MultilineCommentCont);
+ }
+ break;
+
+ default:
+ qWarning() << "Unhandled state" << currentStatus.state().typeStr();
+ break;
+ } // end of state switch
+
+ ++tokenIndex;
+ }
+
+ StateType topState = currentStatus.state().type;
+
+ // if there's no colon on the same line, it's not a label
+ if (topState == StateType::ExpressionOrLabel)
+ enterState(StateType::Expression);
+ // if not followed by an identifier on the same line, it's done
+ else if (topState == StateType::BreakcontinueStatement)
+ leaveState(true);
+
+ topState = currentStatus.state().type;
+
+ // some states might be continued on the next line
+ if (topState == StateType::Expression || topState == StateType::ExpressionOrObjectdefinition
+ || topState == StateType::ObjectliteralAssignment
+ || topState == StateType::TernaryOpAfterColon) {
+ enterState(StateType::ExpressionMaybeContinuation);
+ }
+ // multi-line comment start?
+ if (topState != StateType::MultilineCommentStart && topState != StateType::MultilineCommentCont
+ && currentStatus.lexerState.state.tokenKind == QQmlJSGrammar::T_PARTIAL_COMMENT) {
+ enterState(StateType::MultilineCommentStart);
+ }
+ currentStatus.finalIndent = currentIndent;
+}
+
+// adjusts the indentation of the current line based on the status of the previous one, and what
+// it starts with
+int indentForLineStartingWithToken(const FormatTextStatus &oldStatus, const FormatOptions &,
+ int tokenKind)
+{
+ State topState = oldStatus.state();
+ State previousState = oldStatus.state(1);
+ int indentDepth = oldStatus.finalIndent;
+
+ // keep user-adjusted indent in multiline comments
+ if (topState.type == StateType::MultilineCommentStart
+ || topState.type == StateType::MultilineCommentCont) {
+ if (!Token::lexKindIsInvalid(tokenKind))
+ return -1;
+ }
+ // don't touch multi-line strings at all
+ if (oldStatus.lexerState.state.tokenKind == QQmlJSGrammar::T_PARTIAL_DOUBLE_QUOTE_STRING_LITERAL
+ || oldStatus.lexerState.state.tokenKind == QQmlJSGrammar::T_PARTIAL_SINGLE_QUOTE_STRING_LITERAL
+ || oldStatus.lexerState.state.tokenKind == QQmlJSGrammar::T_PARTIAL_TEMPLATE_HEAD
+ || oldStatus.lexerState.state.tokenKind == QQmlJSGrammar::T_PARTIAL_TEMPLATE_MIDDLE) {
+ return -1;
+ }
+
+ switch (tokenKind) {
+ case QQmlJSGrammar::T_LBRACE:
+ if (topState.type == StateType::Substatement
+ || topState.type == StateType::BindingAssignment
+ || topState.type == StateType::CaseCont) {
+ return topState.savedIndentDepth;
+ }
+ break;
+ case QQmlJSGrammar::T_RBRACE: {
+ if (topState.type == StateType::JsblockOpen && previousState.type == StateType::CaseCont) {
+ return previousState.savedIndentDepth;
+ }
+ for (int i = 0; oldStatus.state(i).type != StateType::TopmostIntro; ++i) {
+ const StateType type = oldStatus.state(i).type;
+ if (type == StateType::ObjectdefinitionOpen || type == StateType::JsblockOpen
+ || type == StateType::SubstatementOpen || type == StateType::ObjectliteralOpen) {
+ return oldStatus.state(i).savedIndentDepth;
+ }
+ }
+ break;
+ }
+ case QQmlJSGrammar::T_RBRACKET:
+ for (int i = 0; oldStatus.state(i).type != StateType::TopmostIntro; ++i) {
+ const StateType type = oldStatus.state(i).type;
+ if (type == StateType::BracketOpen) {
+ return oldStatus.state(i).savedIndentDepth;
+ }
+ }
+ break;
+ case QQmlJSGrammar::T_LBRACKET:
+ case QQmlJSGrammar::T_LPAREN:
+ if (topState.type == StateType::ExpressionMaybeContinuation)
+ return topState.savedIndentDepth;
+ break;
+ case QQmlJSGrammar::T_ELSE:
+ if (topState.type == StateType::MaybeElse) {
+ return oldStatus.state(1).savedIndentDepth;
+ } else if (topState.type == StateType::ExpressionMaybeContinuation) {
+ bool hasElse = false;
+ for (int i = 1; oldStatus.state(i).type != StateType::TopmostIntro; ++i) {
+ const StateType type = oldStatus.state(i).type;
+ if (type == StateType::ElseClause)
+ hasElse = true;
+ if (type == StateType::IfStatement) {
+ if (hasElse) {
+ hasElse = false;
+ } else {
+ return oldStatus.state(i).savedIndentDepth;
+ }
+ }
+ }
+ }
+ break;
+ case QQmlJSGrammar::T_CATCH:
+ case QQmlJSGrammar::T_FINALLY:
+ if (topState.type == StateType::MaybeCatchOrFinally)
+ return oldStatus.state(1).savedIndentDepth;
+ break;
+ case QQmlJSGrammar::T_COLON:
+ if (topState.type == StateType::TernaryOp)
+ return indentDepth - 2;
+ break;
+ case QQmlJSGrammar::T_QUESTION:
+ if (topState.type == StateType::ExpressionMaybeContinuation)
+ return topState.savedIndentDepth;
+ break;
+
+ case QQmlJSGrammar::T_DEFAULT:
+ case QQmlJSGrammar::T_CASE:
+ for (int i = 0; oldStatus.state(i).type != StateType::TopmostIntro; ++i) {
+ const StateType type = oldStatus.state(i).type;
+ if (type == StateType::SwitchStatement || type == StateType::CaseCont) {
+ return oldStatus.state(i).savedIndentDepth;
+ } else if (type == StateType::TopmostIntro) {
+ break;
+ }
+ }
+ break;
+ default:
+ if (Token::lexKindIsDelimiter(tokenKind)
+ && topState.type == StateType::ExpressionMaybeContinuation)
+ return topState.savedIndentDepth;
+
+ break;
+ }
+ return indentDepth;
+}
+
+// sets currentIndent to the correct indent for the current line
+int FormatPartialStatus::indentLine()
+{
+ Q_ASSERT(currentStatus.size() >= 1);
+ int firstToken = (lineTokens.isEmpty() ? QQmlJSGrammar::T_NONE : tokenAt(0).lexKind);
+ int indent = indentForLineStartingWithToken(initialStatus, options, firstToken);
+ recalculateWithIndent(indent);
+ return indent;
+}
+
+int FormatPartialStatus::indentForNewLineAfter() const
+{
+ // should be just currentIndent?
+ int indent = indentForLineStartingWithToken(currentStatus, options, QQmlJSGrammar::T_NONE);
+ if (indent < 0)
+ return currentIndent;
+ return indent;
+}
+
+void FormatPartialStatus::recalculateWithIndent(int indent)
+{
+ if (indent >= 0) {
+ indentOffset = 0;
+ int i = 0;
+ while (i < line.size() && line.at(i).isSpace())
+ ++i;
+ indentOffset = indent - column(i);
+ }
+ currentIndent = initialStatus.finalIndent;
+ auto lexerState = currentStatus.lexerState;
+ currentStatus = initialStatus;
+ currentStatus.lexerState = lexerState;
+ tokenIndex = 0;
+ handleTokens();
+}
+
+FormatPartialStatus formatCodeLine(QStringView line, const FormatOptions &options,
+ const FormatTextStatus &initialStatus)
+{
+ FormatPartialStatus status(line, options, initialStatus);
+
+ status.handleTokens();
+
+ return status;
+}
+
+void FormatPartialStatus::defaultOnEnter(StateType newState, int *indentDepth,
+ int *savedIndentDepth) const
+{
+ const State &parentState = currentStatus.state();
+ const Token &tk = tokenAt(tokenIndex);
+ const int tokenPosition = column(tk.begin());
+ const bool firstToken = (tokenIndex == 0);
+ const bool lastToken = (tokenIndex == lineTokens.size() - 1);
+
+ switch (newState) {
+ case StateType::ObjectdefinitionOpen: {
+ // special case for things like "gradient: Gradient {"
+ if (parentState.type == StateType::BindingAssignment)
+ *savedIndentDepth = currentStatus.state(1).savedIndentDepth;
+
+ if (firstToken)
+ *savedIndentDepth = tokenPosition;
+
+ *indentDepth = *savedIndentDepth + options.indentSize;
+ break;
+ }
+
+ case StateType::BindingOrObjectdefinition:
+ if (firstToken)
+ *indentDepth = *savedIndentDepth = tokenPosition;
+ break;
+
+ case StateType::BindingAssignment:
+ case StateType::ObjectliteralAssignment:
+ if (lastToken)
+ *indentDepth = *savedIndentDepth + options.indentSize;
+ else
+ *indentDepth = column(tokenAt(tokenIndex + 1).begin());
+ break;
+
+ case StateType::ExpressionOrObjectdefinition:
+ *indentDepth = tokenPosition;
+ break;
+
+ case StateType::ExpressionOrLabel:
+ if (*indentDepth == tokenPosition)
+ *indentDepth += 2 * options.indentSize;
+ else
+ *indentDepth = tokenPosition;
+ break;
+
+ case StateType::Expression:
+ if (*indentDepth == tokenPosition) {
+ // expression_or_objectdefinition doesn't want the indent
+ // expression_or_label already has it
+ if (parentState.type != StateType::ExpressionOrObjectdefinition
+ && parentState.type != StateType::ExpressionOrLabel
+ && parentState.type != StateType::BindingAssignment) {
+ *indentDepth += 2 * options.indentSize;
+ }
+ }
+ // expression_or_objectdefinition and expression_or_label have already consumed the
+ // first token
+ else if (parentState.type != StateType::ExpressionOrObjectdefinition
+ && parentState.type != StateType::ExpressionOrLabel) {
+ *indentDepth = tokenPosition;
+ }
+ break;
+
+ case StateType::ExpressionMaybeContinuation:
+ // set indent depth to indent we'd get if the expression ended here
+ for (int i = 1; currentStatus.state(i).type != StateType::TopmostIntro; ++i) {
+ const StateType type = currentStatus.state(i).type;
+ if (FormatTextStatus::isExpressionEndState(type)
+ && !FormatTextStatus::isBracelessState(type)) {
+ *indentDepth = currentStatus.state(i - 1).savedIndentDepth;
+ break;
+ }
+ }
+ break;
+
+ case StateType::BracketOpen:
+ if (parentState.type == StateType::Expression
+ && currentStatus.state(1).type == StateType::BindingAssignment) {
+ *savedIndentDepth = currentStatus.state(2).savedIndentDepth;
+ *indentDepth = *savedIndentDepth + options.indentSize;
+ } else if (parentState.type == StateType::ObjectliteralAssignment) {
+ *savedIndentDepth = parentState.savedIndentDepth;
+ *indentDepth = *savedIndentDepth + options.indentSize;
+ } else if (!lastToken) {
+ *indentDepth = tokenPosition + 1;
+ } else {
+ *indentDepth = *savedIndentDepth + options.indentSize;
+ }
+ break;
+
+ case StateType::FunctionStart:
+ // align to the beginning of the line
+ *savedIndentDepth = *indentDepth = column(tokenAt(0).begin());
+ break;
+
+ case StateType::DoStatementWhileParenOpen:
+ case StateType::StatementWithConditionParenOpen:
+ case StateType::SignalArglistOpen:
+ case StateType::FunctionArglistOpen:
+ case StateType::ParenOpen:
+ if (!lastToken)
+ *indentDepth = tokenPosition + 1;
+ else
+ *indentDepth += options.indentSize;
+ break;
+
+ case StateType::TernaryOp:
+ if (!lastToken)
+ *indentDepth = tokenPosition + tk.length + 1;
+ else
+ *indentDepth += options.indentSize;
+ break;
+
+ case StateType::JsblockOpen:
+ // closing brace should be aligned to case
+ if (parentState.type == StateType::CaseCont) {
+ *savedIndentDepth = parentState.savedIndentDepth;
+ break;
+ }
+ Q_FALLTHROUGH();
+ case StateType::SubstatementOpen:
+ // special case for "foo: {" and "property int foo: {"
+ if (parentState.type == StateType::BindingAssignment)
+ *savedIndentDepth = currentStatus.state(1).savedIndentDepth;
+ *indentDepth = *savedIndentDepth + options.indentSize;
+ break;
+
+ case StateType::Substatement:
+ *indentDepth += options.indentSize;
+ break;
+
+ case StateType::ObjectliteralOpen:
+ if (parentState.type == StateType::Expression
+ || parentState.type == StateType::ObjectliteralAssignment) {
+ // undo the continuation indent of the expression
+ if (currentStatus.state(1).type == StateType::ExpressionOrLabel)
+ *indentDepth = currentStatus.state(1).savedIndentDepth;
+ else
+ *indentDepth = parentState.savedIndentDepth;
+ *savedIndentDepth = *indentDepth;
+ }
+ *indentDepth += options.indentSize;
+ break;
+
+ case StateType::StatementWithCondition:
+ case StateType::TryStatement:
+ case StateType::CatchStatement:
+ case StateType::FinallyStatement:
+ case StateType::IfStatement:
+ case StateType::DoStatement:
+ case StateType::SwitchStatement:
+ if (firstToken || parentState.type == StateType::BindingAssignment)
+ *savedIndentDepth = tokenPosition;
+ // ### continuation
+ *indentDepth = *savedIndentDepth; // + 2*options.indentSize;
+ // special case for 'else if'
+ if (!firstToken && newState == StateType::IfStatement
+ && parentState.type == StateType::Substatement
+ && currentStatus.state(1).type == StateType::ElseClause) {
+ *indentDepth = currentStatus.state(1).savedIndentDepth;
+ *savedIndentDepth = *indentDepth;
+ }
+ break;
+
+ case StateType::MaybeElse:
+ case StateType::MaybeCatchOrFinally: {
+ // set indent to where leave(true) would put it
+ int lastNonEndState = 0;
+ while (!FormatTextStatus::isExpressionEndState(
+ currentStatus.state(lastNonEndState + 1).type))
+ ++lastNonEndState;
+ *indentDepth = currentStatus.state(lastNonEndState).savedIndentDepth;
+ break;
+ }
+
+ case StateType::ConditionOpen:
+ // fixed extra indent when continuing 'if (', but not for 'else if ('
+ if (tokenPosition <= *indentDepth + options.indentSize)
+ *indentDepth += 2 * options.indentSize;
+ else
+ *indentDepth = tokenPosition + 1;
+ break;
+
+ case StateType::CaseStart:
+ *savedIndentDepth = tokenPosition;
+ break;
+
+ case StateType::CaseCont:
+ *indentDepth += options.indentSize;
+ break;
+
+ case StateType::MultilineCommentStart:
+ *indentDepth = tokenPosition + 2;
+ break;
+
+ case StateType::MultilineCommentCont:
+ *indentDepth = tokenPosition;
+ break;
+ default:
+ break;
+ }
+}
+
+} // namespace Dom
+} // namespace QQmlJS
+QT_END_NAMESPACE
diff --git a/src/qmldom/qqmldomcodeformatter_p.h b/src/qmldom/qqmldomcodeformatter_p.h
new file mode 100644
index 0000000000..084272401e
--- /dev/null
+++ b/src/qmldom/qqmldomcodeformatter_p.h
@@ -0,0 +1,275 @@
+// Copyright (C) 2022 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+
+#ifndef QQMLDOMCODEFORMATTER_P_H
+#define QQMLDOMCODEFORMATTER_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 "qqmldomfunctionref_p.h"
+#include "qqmldomscanner_p.h"
+#include "qqmldomlinewriter_p.h"
+
+#include <QtCore/QStack>
+#include <QtCore/QList>
+#include <QtCore/QSet>
+#include <QtCore/QVector>
+#include <QtCore/QMetaObject>
+
+QT_BEGIN_NAMESPACE
+
+namespace QQmlJS {
+namespace Dom {
+
+class QMLDOM_EXPORT FormatTextStatus
+{
+ Q_GADGET
+public:
+ enum class StateType : quint8 {
+ Invalid = 0,
+
+ TopmostIntro, // The first line in a "topmost" definition.
+
+ TopQml, // root state for qml
+ TopJs, // root for js
+ ObjectdefinitionOrJs, // file starts with identifier
+
+ MultilineCommentStart,
+ MultilineCommentCont,
+
+ ImportStart, // after 'import'
+ ImportMaybeDotOrVersionOrAs, // after string or identifier
+ ImportDot, // after .
+ ImportMaybeAs, // after version
+ ImportAs,
+
+ PropertyStart, // after 'property'
+ PropertyModifiers, // after 'default' or readonly
+ RequiredProperty, // after required
+ PropertyListOpen, // after 'list' as a type
+ PropertyName, // after the type
+ PropertyMaybeInitializer, // after the identifier
+ ComponentStart, // after component
+ ComponentName, // after component Name
+
+ TypeAnnotation, // after a : starting a type annotation
+ TypeParameter, // after a < in a type annotation (starting type parameters)
+
+ EnumStart, // after 'enum'
+
+ SignalStart, // after 'signal'
+ SignalMaybeArglist, // after identifier
+ SignalArglistOpen, // after '('
+
+ FunctionStart, // after 'function'
+ FunctionArglistOpen, // after '(' starting function argument list
+ FunctionArglistClosed, // after ')' in argument list, expecting '{'
+
+ BindingOrObjectdefinition, // after an identifier
+
+ BindingAssignment, // after : in a binding
+ ObjectdefinitionOpen, // after {
+
+ Expression,
+ ExpressionContinuation, // at the end of the line, when the next line definitely is a
+ // continuation
+ ExpressionMaybeContinuation, // at the end of the line, when the next line may be an
+ // expression
+ ExpressionOrObjectdefinition, // after a binding starting with an identifier ("x: foo")
+ ExpressionOrLabel, // when expecting a statement and getting an identifier
+
+ ParenOpen, // opening ( in expression
+ BracketOpen, // opening [ in expression
+ ObjectliteralOpen, // opening { in expression
+
+ ObjectliteralAssignment, // after : in object literal
+
+ BracketElementStart, // after starting bracket_open or after ',' in bracket_open
+ BracketElementMaybeObjectdefinition, // after an identifier in bracket_element_start
+
+ TernaryOp, // The ? : operator
+ TernaryOpAfterColon, // after the : in a ternary
+
+ JsblockOpen,
+
+ EmptyStatement, // for a ';', will be popped directly
+ BreakcontinueStatement, // for continue/break, may be followed by identifier
+
+ IfStatement, // After 'if'
+ MaybeElse, // after the first substatement in an if
+ ElseClause, // The else line of an if-else construct.
+
+ ConditionOpen, // Start of a condition in 'if', 'while', entered after opening paren
+
+ Substatement, // The first line after a conditional or loop construct.
+ SubstatementOpen, // The brace that opens a substatement block.
+
+ LabelledStatement, // after a label
+
+ ReturnStatement, // After 'return'
+ ThrowStatement, // After 'throw'
+
+ StatementWithCondition, // After the 'for', 'while', ... token
+ StatementWithConditionParenOpen, // While inside the (...)
+
+ TryStatement, // after 'try'
+ CatchStatement, // after 'catch', nested in try_statement
+ FinallyStatement, // after 'finally', nested in try_statement
+ MaybeCatchOrFinally, // after ther closing '}' of try_statement and catch_statement,
+ // nested in try_statement
+
+ DoStatement, // after 'do'
+ DoStatementWhileParenOpen, // after '(' in while clause
+
+ SwitchStatement, // After 'switch' token
+ CaseStart, // after a 'case' or 'default' token
+ CaseCont // after the colon in a case/default
+ };
+ Q_ENUM(StateType)
+
+ static QString stateToString(StateType type);
+
+ class State
+ {
+ public:
+ quint16 savedIndentDepth = 0;
+ StateType type = StateType::Invalid;
+ bool operator==(const State &other) const
+ {
+ return type == other.type && savedIndentDepth == other.savedIndentDepth;
+ }
+ QString typeStr() const { return FormatTextStatus::stateToString(type); }
+ };
+
+ static bool isBracelessState(StateType type)
+ {
+ return type == StateType::IfStatement || type == StateType::ElseClause
+ || type == StateType::Substatement || type == StateType::BindingAssignment
+ || type == StateType::BindingOrObjectdefinition;
+ }
+
+ static bool isExpressionEndState(StateType type)
+ {
+ return type == StateType::TopmostIntro || type == StateType::TopJs
+ || type == StateType::ObjectdefinitionOpen || type == StateType::DoStatement
+ || type == StateType::JsblockOpen || type == StateType::SubstatementOpen
+ || type == StateType::BracketOpen || type == StateType::ParenOpen
+ || type == StateType::CaseCont || type == StateType::ObjectliteralOpen;
+ }
+
+ static FormatTextStatus initialStatus(int baseIndent = 0)
+ {
+ return FormatTextStatus {
+ Scanner::State {},
+ QVector<State>({ State { quint16(baseIndent), StateType::TopmostIntro } }), baseIndent
+ };
+ }
+
+ size_t size() const { return states.size(); }
+
+ State state(int belowTop = 0) const;
+
+ void pushState(StateType type, quint16 savedIndentDepth)
+ {
+ states.append(State { savedIndentDepth, type });
+ }
+
+ State popState()
+ {
+ if (states.isEmpty()) {
+ Q_ASSERT(false);
+ return State();
+ }
+ State res = states.last();
+ states.removeLast();
+ return res;
+ }
+
+ Scanner::State lexerState = {};
+ QVector<State> states;
+ int finalIndent = 0;
+};
+
+class QMLDOM_EXPORT FormatPartialStatus
+{
+ Q_GADGET
+public:
+
+ using OnEnterCallback =
+ function_ref<void(FormatTextStatus::StateType newState, int *indentDepth,
+ int *savedIndentDepth, const FormatPartialStatus &fStatus)>;
+
+ // to determine whether a line was joined, Tokenizer needs a
+ // newline character at the end, lease ensure that line contains it
+ FormatPartialStatus() = default;
+ FormatPartialStatus(const FormatPartialStatus &o) = default;
+ FormatPartialStatus &operator=(const FormatPartialStatus &o) = default;
+ FormatPartialStatus(QStringView line, const FormatOptions &options,
+ const FormatTextStatus &initialStatus)
+ : line(line),
+ options(options),
+ initialStatus(initialStatus),
+ currentStatus(initialStatus),
+ currentIndent(0),
+ tokenIndex(0)
+ {
+ Scanner::State startState = initialStatus.lexerState;
+ currentIndent = initialStatus.finalIndent;
+ Scanner tokenize;
+ lineTokens = tokenize(line, startState);
+ currentStatus.lexerState = tokenize.state();
+ }
+
+ void enterState(FormatTextStatus::StateType newState);
+ void leaveState(bool statementDone);
+ void turnIntoState(FormatTextStatus::StateType newState);
+
+ const Token &tokenAt(int idx) const;
+ int tokenCount() const { return lineTokens.size(); }
+ int column(int index) const;
+ QStringView tokenText(const Token &token) const;
+ void handleTokens();
+
+ bool tryInsideExpression(bool alsoExpression);
+ bool tryStatement();
+
+ void defaultOnEnter(FormatTextStatus::StateType newState, int *indentDepth,
+ int *savedIndentDepth) const;
+
+ int indentLine();
+ int indentForNewLineAfter() const;
+ void recalculateWithIndent(int indent);
+
+ void dump() const;
+
+ QStringView line;
+ FormatOptions options;
+ FormatTextStatus initialStatus;
+ FormatTextStatus currentStatus;
+ int indentOffset = 0;
+ int currentIndent = 0;
+ QList<Token> lineTokens;
+ int tokenIndex = 0;
+};
+
+QMLDOM_EXPORT int indentForLineStartingWithToken(const FormatTextStatus &oldStatus,
+ const FormatOptions &options,
+ int token = QQmlJSGrammar::T_ERROR);
+
+QMLDOM_EXPORT FormatPartialStatus formatCodeLine(QStringView line, const FormatOptions &options,
+ const FormatTextStatus &initialStatus);
+
+} // namespace Dom
+} // namespace QQmlJs
+QT_END_NAMESPACE
+#endif // QQMLDOMCODEFORMATTER_P_H
diff --git a/src/qmldom/qqmldomcomments.cpp b/src/qmldom/qqmldomcomments.cpp
new file mode 100644
index 0000000000..2d3a8cde70
--- /dev/null
+++ b/src/qmldom/qqmldomcomments.cpp
@@ -0,0 +1,766 @@
+// Copyright (C) 2021 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+
+#include "qqmldomcomments_p.h"
+#include "qqmldomoutwriter_p.h"
+#include "qqmldomlinewriter_p.h"
+#include "qqmldomelements_p.h"
+#include "qqmldomexternalitems_p.h"
+#include "qqmldomastdumper_p.h"
+#include "qqmldomattachedinfo_p.h"
+
+#include <QtQml/private/qqmljsastvisitor_p.h>
+#include <QtQml/private/qqmljsast_p.h>
+#include <QtQml/private/qqmljslexer_p.h>
+
+#include <QtCore/QSet>
+
+#include <variant>
+
+static Q_LOGGING_CATEGORY(commentsLog, "qt.qmldom.comments", QtWarningMsg);
+
+QT_BEGIN_NAMESPACE
+namespace QQmlJS {
+namespace Dom {
+
+/*!
+\internal
+\class QQmlJS::Dom::AstComments
+
+\brief Associates comments with AST::Node *
+
+Comments are associated to the largest closest node with the
+following algorithm:
+\list
+\li comments of a node can either be preComments or postComments (before
+or after the element)
+\li define start and end for each element, if two elements start (or end)
+ at the same place the first (larger) wins.
+\li associate the comments either with the element just before or
+just after unless the comments is *inside* an element (meaning that
+going back there is a start before finding an end, or going forward an
+end is met before a start).
+\li to choose between the element before or after, we look at the start
+of the comment, if it is on a new line then associating it as
+preComment to the element after is preferred, otherwise post comment
+of the previous element (inline element).
+This is the only space dependent choice, making comment assignment
+quite robust
+\li if the comment is intrinsically inside all elements then it is moved
+to before the smallest element.
+This is the largest reorganization performed, and it is still quite
+small and difficult to trigger.
+\li the comments are stored with the whitespace surrounding them, from
+the preceding newline (and recording if a newline is required before
+it) until the newline after.
+This allows a better reproduction of the comments.
+\endlist
+*/
+/*!
+\class QQmlJS::Dom::CommentInfo
+
+\brief Extracts various pieces and information out of a rawComment string
+
+Comments store a string (rawComment) with comment characters (//,..) and spaces.
+Sometime one wants just the comment, the commentcharacters, the space before the comment,....
+CommentInfo gets such a raw comment string and makes the various pieces available
+*/
+CommentInfo::CommentInfo(QStringView rawComment) : rawComment(rawComment)
+{
+ commentBegin = 0;
+ while (commentBegin < quint32(rawComment.size()) && rawComment.at(commentBegin).isSpace()) {
+ if (rawComment.at(commentBegin) == QLatin1Char('\n'))
+ hasStartNewline = true;
+ ++commentBegin;
+ }
+ if (commentBegin < quint32(rawComment.size())) {
+ QString expectedEnd;
+ switch (rawComment.at(commentBegin).unicode()) {
+ case '/':
+ commentStartStr = rawComment.mid(commentBegin, 2);
+ if (commentStartStr == u"/*") {
+ expectedEnd = QStringLiteral(u"*/");
+ } else {
+ if (commentStartStr == u"//") {
+ expectedEnd = QStringLiteral(u"\n");
+ } else {
+ warnings.append(tr("Unexpected comment start %1").arg(commentStartStr));
+ }
+ }
+ break;
+ case '#':
+ commentStartStr = rawComment.mid(commentBegin, 1);
+ expectedEnd = QStringLiteral(u"\n");
+ break;
+ default:
+ commentStartStr = rawComment.mid(commentBegin, 1);
+ warnings.append(tr("Unexpected comment start %1").arg(commentStartStr));
+ break;
+ }
+ commentEnd = commentBegin + commentStartStr.size();
+ quint32 rawEnd = quint32(rawComment.size());
+ commentContentEnd = commentContentBegin = commentEnd;
+ QChar e1 = ((expectedEnd.isEmpty()) ? QChar::fromLatin1(0) : expectedEnd.at(0));
+ while (commentEnd < rawEnd) {
+ QChar c = rawComment.at(commentEnd);
+ if (c == e1) {
+ if (expectedEnd.size() > 1) {
+ if (++commentEnd < rawEnd && rawComment.at(commentEnd) == expectedEnd.at(1)) {
+ Q_ASSERT(expectedEnd.size() == 2);
+ commentEndStr = rawComment.mid(++commentEnd - 2, 2);
+ break;
+ } else {
+ commentContentEnd = commentEnd;
+ }
+ } else {
+ // Comment ends with \n, treat as it is not part of the comment but post whitespace
+ commentEndStr = rawComment.mid(commentEnd - 1, 1);
+ break;
+ }
+ } else if (!c.isSpace()) {
+ commentContentEnd = commentEnd;
+ } else if (c == QLatin1Char('\n')) {
+ ++nContentNewlines;
+ } else if (c == QLatin1Char('\r')) {
+ if (expectedEnd == QStringLiteral(u"\n")) {
+ if (commentEnd + 1 < rawEnd
+ && rawComment.at(commentEnd + 1) == QLatin1Char('\n')) {
+ ++commentEnd;
+ commentEndStr = rawComment.mid(++commentEnd - 2, 2);
+ } else {
+ commentEndStr = rawComment.mid(++commentEnd - 1, 1);
+ }
+ break;
+ } else if (commentEnd + 1 == rawEnd
+ || rawComment.at(commentEnd + 1) != QLatin1Char('\n')) {
+ ++nContentNewlines;
+ }
+ }
+ ++commentEnd;
+ }
+
+ if (commentEnd > 0
+ && (rawComment.at(commentEnd - 1) == QLatin1Char('\n')
+ || rawComment.at(commentEnd - 1) == QLatin1Char('\r')))
+ hasEndNewline = true;
+ quint32 i = commentEnd;
+ while (i < rawEnd && rawComment.at(i).isSpace()) {
+ if (rawComment.at(i) == QLatin1Char('\n') || rawComment.at(i) == QLatin1Char('\r'))
+ hasEndNewline = true;
+ ++i;
+ }
+ if (i < rawEnd) {
+ warnings.append(tr("Non whitespace char %1 after comment end at %2")
+ .arg(rawComment.at(i))
+ .arg(i));
+ }
+ }
+}
+
+/*!
+\class QQmlJS::Dom::Comment
+
+\brief Represents a comment
+
+Comments are not needed for execute the program, so they are aimed to the programmer,
+and have few functions: explaining code, adding extra info/context (who did write,
+when licensing,...) or disabling code.
+Being for the programmer and being non functional it is difficult to treat them properly.
+So preserving them as much as possible is the best course of action.
+
+To acheive this comment is represented by
+\list
+\li newlinesBefore: the number of newlines before the comment, to preserve spacing between
+comments (the extraction routines limit this to 2 at most, i.e. a single empty line) \li
+rawComment: a string with the actual comment including whitespace before and after and the
+comment characters (whitespace before is limited to spaces/tabs to preserve indentation or
+spacing just before starting the comment) \endlist The rawComment is a bit annoying if one wants
+to change the comment, or extract information from it. For this reason info gives access to the
+various elements of it: the comment characters #, // or /
+*, the space before it, and the actual comment content.
+
+the comments are stored with the whitespace surrounding them, from
+the preceding newline (and recording if a newline is required before
+it) until the newline after.
+
+A comment has methods to write it out again (write) and expose it to the Dom
+(iterateDirectSubpaths).
+*/
+
+/*!
+\brief Expose attributes to the Dom
+*/
+bool Comment::iterateDirectSubpaths(const DomItem &self, DirectVisitor visitor) const
+{
+ bool cont = true;
+ cont = cont && self.dvValueField(visitor, Fields::rawComment, rawComment());
+ cont = cont && self.dvValueField(visitor, Fields::newlinesBefore, newlinesBefore());
+ return cont;
+}
+
+void Comment::write(OutWriter &lw, SourceLocation *commentLocation) const
+{
+ if (newlinesBefore())
+ lw.ensureNewline(newlinesBefore());
+ CommentInfo cInfo = info();
+ lw.ensureSpace(cInfo.preWhitespace());
+ QStringView cBody = cInfo.comment();
+ PendingSourceLocationId cLoc = lw.lineWriter.startSourceLocation(commentLocation);
+ lw.write(cBody.mid(0, 1));
+ bool indentOn = lw.indentNextlines;
+ lw.indentNextlines = false;
+ lw.write(cBody.mid(1));
+ lw.indentNextlines = indentOn;
+ lw.lineWriter.endSourceLocation(cLoc);
+ lw.write(cInfo.postWhitespace());
+}
+
+/*!
+\class QQmlJS::Dom::CommentedElement
+\brief Keeps the comment associated with an element
+
+A comment can be attached to an element (that is always a range of the file with a start and
+end) only in two ways: it can precede the region (preComments), or follow it (postComments).
+*/
+
+/*!
+\class QQmlJS::Dom::RegionComments
+\brief Keeps the comments associated with a DomItem
+
+A DomItem can be more complex that just a start/end, it can have multiple regions, for example
+a return or a function token might define a region.
+The empty string is the region that represents the whole element.
+
+Every region has a name, and should be written out using the OutWriter.writeRegion (or
+startRegion/ EndRegion). Region comments keeps a mapping containing them.
+*/
+
+bool CommentedElement::iterateDirectSubpaths(const DomItem &self, DirectVisitor visitor) const
+{
+ bool cont = true;
+ cont = cont && self.dvWrapField(visitor, Fields::preComments, m_preComments);
+ cont = cont && self.dvWrapField(visitor, Fields::postComments, m_postComments);
+ return cont;
+}
+
+void CommentedElement::writePre(OutWriter &lw, QList<SourceLocation> *locs) const
+{
+ if (locs)
+ locs->resize(m_preComments.size());
+ int i = 0;
+ for (const Comment &c : m_preComments)
+ c.write(lw, (locs ? &((*locs)[i++]) : nullptr));
+}
+
+void CommentedElement::writePost(OutWriter &lw, QList<SourceLocation> *locs) const
+{
+ if (locs)
+ locs->resize(m_postComments.size());
+ int i = 0;
+ for (const Comment &c : m_postComments)
+ c.write(lw, (locs ? &((*locs)[i++]) : nullptr));
+}
+
+using namespace QQmlJS::AST;
+
+class RegionRef
+{
+public:
+ Path path; // store the MutableDomItem instead?
+ FileLocationRegion regionName;
+};
+
+// internal class to keep a reference either to an AST::Node* or a region of a DomItem and the
+// size of that region
+class ElementRef
+{
+public:
+ ElementRef(AST::Node *node, quint32 size) : element(node), size(size) { }
+ ElementRef(const Path &path, FileLocationRegion region, quint32 size)
+ : element(RegionRef{ path, region }), size(size)
+ {
+ }
+ operator bool() const
+ {
+ return (element.index() == 0 && std::get<0>(element)) || element.index() == 1 || size != 0;
+ }
+ ElementRef() = default;
+
+ std::variant<AST::Node *, RegionRef> element;
+ quint32 size = 0;
+};
+
+/*!
+\class QQmlJS::Dom::VisitAll
+\brief A vistor that visits all the AST:Node
+
+The default visitor does not necessarily visit all nodes, because some part
+of the AST are typically handled manually. This visitor visits *all* AST
+elements contained.
+
+Note: Subclasses should take care to call the parent (i.e. this) visit/endVisit
+methods when overriding them, to guarantee that all element are really visited
+*/
+
+/*!
+returns a set with all Ui* Nodes (i.e. the top level non javascript Qml)
+*/
+QSet<int> VisitAll::uiKinds()
+{
+ static QSet<int> res({ AST::Node::Kind_UiObjectMemberList, AST::Node::Kind_UiArrayMemberList,
+ AST::Node::Kind_UiParameterList, AST::Node::Kind_UiHeaderItemList,
+ AST::Node::Kind_UiEnumMemberList, AST::Node::Kind_UiAnnotationList,
+
+ AST::Node::Kind_UiArrayBinding, AST::Node::Kind_UiImport,
+ AST::Node::Kind_UiObjectBinding, AST::Node::Kind_UiObjectDefinition,
+ AST::Node::Kind_UiInlineComponent, AST::Node::Kind_UiObjectInitializer,
+#if QT_VERSION >= QT_VERSION_CHECK(6, 6, 0)
+ AST::Node::Kind_UiPragmaValueList,
+#endif
+ AST::Node::Kind_UiPragma, AST::Node::Kind_UiProgram,
+ AST::Node::Kind_UiPublicMember, AST::Node::Kind_UiQualifiedId,
+ AST::Node::Kind_UiScriptBinding, AST::Node::Kind_UiSourceElement,
+ AST::Node::Kind_UiEnumDeclaration, AST::Node::Kind_UiVersionSpecifier,
+ AST::Node::Kind_UiRequired, AST::Node::Kind_UiAnnotation });
+ return res;
+}
+
+// internal private class to set all the starts/ends of the nodes/regions
+class AstRangesVisitor final : protected VisitAll
+{
+public:
+ AstRangesVisitor() = default;
+
+ void addNodeRanges(AST::Node *rootNode);
+ void addItemRanges(
+ const DomItem &item, const FileLocations::Tree &itemLocations, const Path &currentP);
+
+ void throwRecursionDepthError() override { }
+
+ static const QSet<int> kindsToSkip();
+ static bool shouldSkipRegion(const DomItem &item, FileLocationRegion region);
+
+ bool preVisit(Node *n) override
+ {
+ if (!kindsToSkip().contains(n->kind)) {
+ quint32 start = n->firstSourceLocation().begin();
+ quint32 end = n->lastSourceLocation().end();
+ if (!starts.contains(start))
+ starts.insert(start, { n, end - start });
+ if (!ends.contains(end))
+ ends.insert(end, { n, end - start });
+ }
+ return true;
+ }
+
+ QMap<quint32, ElementRef> starts;
+ QMap<quint32, ElementRef> ends;
+};
+
+void AstRangesVisitor::addNodeRanges(AST::Node *rootNode)
+{
+ AST::Node::accept(rootNode, this);
+}
+
+void AstRangesVisitor::addItemRanges(
+ const DomItem &item, const FileLocations::Tree &itemLocations, const Path &currentP)
+{
+ if (!itemLocations) {
+ if (item)
+ qCWarning(commentsLog) << "reached item" << item.canonicalPath() << "without locations";
+ return;
+ }
+ DomItem comments = item.field(Fields::comments);
+ if (comments) {
+ auto regs = itemLocations->info().regions;
+ for (auto it = regs.cbegin(), end = regs.cend(); it != end; ++it) {
+ quint32 startI = it.value().begin();
+ quint32 endI = it.value().end();
+
+ if (!shouldSkipRegion(item, it.key())) {
+ if (!starts.contains(startI))
+ starts.insert(startI, { currentP, it.key(), quint32(endI - startI) });
+ if (!ends.contains(endI))
+ ends.insert(endI, { currentP, it.key(), endI - startI });
+ }
+ }
+ }
+ {
+ auto subMaps = itemLocations->subItems();
+ for (auto it = subMaps.begin(), end = subMaps.end(); it != end; ++it) {
+ addItemRanges(item.path(it.key()),
+ std::static_pointer_cast<AttachedInfoT<FileLocations>>(it.value()),
+ currentP.path(it.key()));
+ }
+ }
+}
+
+const QSet<int> AstRangesVisitor::kindsToSkip()
+{
+ static QSet<int> res = QSet<int>({
+ AST::Node::Kind_ArgumentList,
+ AST::Node::Kind_ElementList,
+ AST::Node::Kind_FormalParameterList,
+ AST::Node::Kind_ImportsList,
+ AST::Node::Kind_ExportsList,
+ AST::Node::Kind_PropertyDefinitionList,
+ AST::Node::Kind_StatementList,
+ AST::Node::Kind_VariableDeclarationList,
+ AST::Node::Kind_ClassElementList,
+ AST::Node::Kind_PatternElementList,
+ AST::Node::Kind_PatternPropertyList,
+ AST::Node::Kind_TypeArgument,
+ })
+ .unite(VisitAll::uiKinds());
+ return res;
+}
+
+/*! \internal
+ \brief returns true if comments should skip attaching to this region
+*/
+bool AstRangesVisitor::shouldSkipRegion(const DomItem &item, FileLocationRegion region)
+{
+ switch (item.internalKind()) {
+ case DomType::EnumDecl: {
+ return (region == FileLocationRegion::IdentifierRegion)
+ || (region == FileLocationRegion::EnumKeywordRegion);
+ }
+ case DomType::EnumItem: {
+ return (region == FileLocationRegion::IdentifierRegion)
+ || (region == FileLocationRegion::EnumValueRegion);
+ }
+ case DomType::QmlObject: {
+ return (region == FileLocationRegion::RightBraceRegion
+ || region == FileLocationRegion::LeftBraceRegion);
+ }
+ case DomType::Import:
+ case DomType::ImportScope:
+ return region == FileLocationRegion::IdentifierRegion;
+ default:
+ return false;
+ }
+ Q_UNREACHABLE_RETURN(false);
+}
+
+class CommentLinker
+{
+public:
+ CommentLinker(QStringView code, ElementRef &commentedElement, const AstRangesVisitor &ranges, quint32 &lastPostCommentPostEnd,
+ const SourceLocation &commentLocation)
+ : m_code{ code },
+ m_commentedElement{ commentedElement },
+ m_lastPostCommentPostEnd{ lastPostCommentPostEnd },
+ m_ranges{ ranges },
+ m_commentLocation { commentLocation },
+ m_startElement{ m_ranges.starts.lowerBound(commentLocation.begin()) },
+ m_endElement{ m_ranges.ends.lowerBound(commentLocation.end()) },
+ m_spaces{findSpacesAroundComment()}
+ {
+ }
+
+ void linkCommentWithElement()
+ {
+ if (m_spaces.preNewline < 1) {
+ checkElementBeforeComment();
+ checkElementAfterComment();
+ } else {
+ checkElementAfterComment();
+ checkElementBeforeComment();
+ }
+ if (!m_commentedElement)
+ checkElementInside();
+ }
+
+ [[nodiscard]] Comment createComment() const
+ {
+ const auto [preSpacesIndex, postSpacesIndex, preNewlineCount] = m_spaces;
+ return Comment{ m_code.mid(preSpacesIndex, quint32(postSpacesIndex) - preSpacesIndex),
+ m_commentLocation,
+ static_cast<int>(preNewlineCount),
+ m_commentType};
+ }
+
+private:
+ struct SpaceTrace
+ {
+ quint32 iPre;
+ qsizetype iPost;
+ int preNewline;
+ };
+
+ /*! \internal
+ \brief Returns a Comment data
+ Comment starts from the first non-newline and non-space character preceding
+ the comment start characters. For example, "\n\n // A comment \n\n\n", we
+ hold the prenewlines count (2). PostNewlines are part of the Comment structure
+ but they are not regarded while writing since they could be a part of prenewlines
+ of a following comment.
+ */
+ [[nodiscard]] SpaceTrace findSpacesAroundComment() const
+ {
+ quint32 iPre = m_commentLocation.begin();
+ int preNewline = 0;
+ int postNewline = 0;
+ QStringView commentStartStr;
+ while (iPre > 0) {
+ QChar c = m_code.at(iPre - 1);
+ if (!c.isSpace()) {
+ if (commentStartStr.isEmpty() && (c == QLatin1Char('*') || c == QLatin1Char('/'))
+ && iPre - 1 > 0 && m_code.at(iPre - 2) == QLatin1Char('/')) {
+ commentStartStr = m_code.mid(iPre - 2, 2);
+ --iPre;
+ } else {
+ break;
+ }
+ } else if (c == QLatin1Char('\n') || c == QLatin1Char('\r')) {
+ preNewline = 1;
+ // possibly add an empty line if it was there (but never more than one)
+ int i = iPre - 1;
+ if (c == QLatin1Char('\n') && i > 0 && m_code.at(i - 1) == QLatin1Char('\r'))
+ --i;
+ while (i > 0 && m_code.at(--i).isSpace()) {
+ c = m_code.at(i);
+ if (c == QLatin1Char('\n') || c == QLatin1Char('\r')) {
+ ++preNewline;
+ break;
+ }
+ }
+ break;
+ }
+ --iPre;
+ }
+ if (iPre == 0)
+ preNewline = 1;
+ qsizetype iPost = m_commentLocation.end();
+ while (iPost < m_code.size()) {
+ QChar c = m_code.at(iPost);
+ if (!c.isSpace()) {
+ if (!commentStartStr.isEmpty() && commentStartStr.at(1) == QLatin1Char('*')
+ && c == QLatin1Char('*') && iPost + 1 < m_code.size()
+ && m_code.at(iPost + 1) == QLatin1Char('/')) {
+ commentStartStr = QStringView();
+ ++iPost;
+ } else {
+ break;
+ }
+ } else {
+ if (c == QLatin1Char('\n')) {
+ ++postNewline;
+ if (iPost + 1 < m_code.size() && m_code.at(iPost + 1) == QLatin1Char('\n')) {
+ ++iPost;
+ ++postNewline;
+ }
+ } else if (c == QLatin1Char('\r')) {
+ if (iPost + 1 < m_code.size() && m_code.at(iPost + 1) == QLatin1Char('\n')) {
+ ++iPost;
+ ++postNewline;
+ }
+ }
+ }
+ ++iPost;
+ if (postNewline > 1)
+ break;
+ }
+
+ return {iPre, iPost, preNewline};
+ }
+
+ // tries to associate comment as a postComment to currentElement
+ void checkElementBeforeComment()
+ {
+ if (m_commentedElement)
+ return;
+ // prefer post comment attached to preceding element
+ auto preEnd = m_endElement;
+ auto preStart = m_startElement;
+ if (preEnd != m_ranges.ends.begin()) {
+ --preEnd;
+ if (m_startElement == m_ranges.starts.begin() || (--preStart).key() < preEnd.key()) {
+ // iStart == begin should never happen
+ // check that we do not have operators (or in general other things) between
+ // preEnd and this because inserting a newline too ealy might invalidate the
+ // expression (think a + //comment\n b ==> a // comment\n + b), in this
+ // case attaching as preComment of iStart (b in the example) should be
+ // preferred as it is safe
+ quint32 i = m_spaces.iPre;
+ while (i != 0 && m_code.at(--i).isSpace())
+ ;
+ if (i <= preEnd.key() || i < m_lastPostCommentPostEnd
+ || m_endElement == m_ranges.ends.end()) {
+ m_commentedElement = preEnd.value();
+ m_commentType = Comment::Post;
+ m_lastPostCommentPostEnd = m_spaces.iPost + 1; // ensure the previous check works
+ // with multiple post comments
+ }
+ }
+ }
+ }
+ // tries to associate comment as a preComment to currentElement
+ void checkElementAfterComment()
+ {
+ if (m_commentedElement)
+ return;
+ if (m_startElement != m_ranges.starts.end()) {
+ // try to add a pre comment of following element
+ if (m_endElement == m_ranges.ends.end() || m_endElement.key() > m_startElement.key()) {
+ // there is no end of element before iStart begins
+ // associate the comment as preComment of iStart
+ // (btw iEnd == end should never happen here)
+ m_commentedElement = m_startElement.value();
+ return;
+ }
+ }
+ if (m_startElement == m_ranges.starts.begin()) {
+ Q_ASSERT(m_startElement != m_ranges.starts.end());
+ // we are before the first node (should be handled already by previous case)
+ m_commentedElement = m_startElement.value();
+ }
+ }
+ void checkElementInside()
+ {
+ if (m_commentedElement)
+ return;
+ auto preStart = m_startElement;
+ if (m_startElement == m_ranges.starts.begin()) {
+ m_commentedElement = m_startElement.value(); // checkElementAfter should have handled this
+ return;
+ } else {
+ --preStart;
+ }
+ // we are inside a node, actually inside both n1 and n2 (which might be the same)
+ // add to pre of the smallest between n1 and n2.
+ // This is needed because if there are multiple nodes starting/ending at the same
+ // place we store only the first (i.e. largest)
+ ElementRef n1 = preStart.value();
+ ElementRef n2 = m_endElement.value();
+ if (n1.size > n2.size)
+ m_commentedElement = n2;
+ else
+ m_commentedElement = n1;
+ }
+private:
+ QStringView m_code;
+ ElementRef &m_commentedElement;
+ quint32 &m_lastPostCommentPostEnd;
+ Comment::CommentType m_commentType = Comment::Pre;
+ const AstRangesVisitor &m_ranges;
+ const SourceLocation &m_commentLocation;
+
+ using RangesIterator = decltype(m_ranges.starts.begin());
+ const RangesIterator m_startElement;
+ const RangesIterator m_endElement;
+ SpaceTrace m_spaces;
+};
+
+/*!
+\class QQmlJS::Dom::AstComments
+\brief Stores the comments associated with javascript AST::Node pointers
+*/
+bool AstComments::iterateDirectSubpaths(const DomItem &self, DirectVisitor visitor) const
+{
+ // TODO: QTBUG-123645
+ // Revert this commit to reproduce crash with tst_qmldomitem::doNotCrashAtAstComments
+ QList<Comment> pre;
+ QList<Comment> post;
+ for (const auto &commentedElement : commentedElements().values()) {
+ pre.append(commentedElement.preComments());
+ post.append(commentedElement.postComments());
+ }
+ if (!pre.isEmpty())
+ self.dvWrapField(visitor, Fields::preComments, pre);
+ if (!post.isEmpty())
+ self.dvWrapField(visitor, Fields::postComments, post);
+
+ return false;
+}
+
+CommentCollector::CommentCollector(MutableDomItem item)
+ : m_rootItem{ std::move(item) },
+ m_fileLocations{ FileLocations::treeOf(m_rootItem.item()) }
+{
+}
+
+void CommentCollector::collectComments()
+{
+ if (std::shared_ptr<ScriptExpression> scriptPtr = m_rootItem.ownerAs<ScriptExpression>()) {
+ return collectComments(scriptPtr->engine(), scriptPtr->ast(), scriptPtr->astComments());
+ } else if (std::shared_ptr<QmlFile> qmlFilePtr = m_rootItem.ownerAs<QmlFile>()) {
+ return collectComments(qmlFilePtr->engine(), qmlFilePtr->ast(), qmlFilePtr->astComments());
+ } else {
+ qCWarning(commentsLog)
+ << "collectComments works with QmlFile and ScriptExpression, not with"
+ << m_rootItem.item().internalKindStr();
+ }
+}
+
+/*! \internal
+ \brief Collects and associates comments with javascript AST::Node pointers
+ or with MutableDomItem
+*/
+void CommentCollector::collectComments(
+ const std::shared_ptr<Engine> &engine, AST::Node *rootNode,
+ const std::shared_ptr<AstComments> &astComments)
+{
+ if (!rootNode)
+ return;
+ AstRangesVisitor ranges;
+ ranges.addItemRanges(m_rootItem.item(), m_fileLocations, Path());
+ ranges.addNodeRanges(rootNode);
+ QStringView code = engine->code();
+ quint32 lastPostCommentPostEnd = 0;
+ for (const SourceLocation &commentLocation : engine->comments()) {
+ // collect whitespace before and after cLoc -> iPre..iPost contains whitespace,
+ // do not add newline before, but add the one after
+ ElementRef elementToBeLinked;
+ CommentLinker linker(code, elementToBeLinked, ranges, lastPostCommentPostEnd, commentLocation);
+ linker.linkCommentWithElement();
+ const auto comment = linker.createComment();
+
+ if (!elementToBeLinked) {
+ qCWarning(commentsLog) << "Could not assign comment at" << sourceLocationToQCborValue(commentLocation)
+ << "adding before root node";
+ if (m_rootItem && (m_fileLocations || !rootNode)) {
+ elementToBeLinked.element = RegionRef{ Path(), MainRegion };
+ elementToBeLinked.size = FileLocations::region(m_fileLocations, MainRegion).length;
+ } else if (rootNode) {
+ elementToBeLinked.element = rootNode;
+ elementToBeLinked.size = rootNode->lastSourceLocation().end() - rootNode->firstSourceLocation().begin();
+ }
+ }
+
+ if (const auto *const commentNode = std::get_if<AST::Node *>(&elementToBeLinked.element)) {
+ auto &commentedElement = astComments->commentedElements()[*commentNode];
+ commentedElement.addComment(comment);
+ } else if (const auto * const regionRef = std::get_if<RegionRef>(&elementToBeLinked.element)) {
+ MutableDomItem regionComments = m_rootItem.item()
+ .path(regionRef->path)
+ .field(Fields::comments);
+ if (auto *regionCommentsPtr = regionComments.mutableAs<RegionComments>())
+ regionCommentsPtr->addComment(comment, regionRef->regionName);
+ else
+ Q_ASSERT(false && "Cannot attach to region comments");
+ } else {
+ qCWarning(commentsLog)
+ << "Failed: no item or node to attach comment" << comment.rawComment();
+ }
+ }
+}
+
+bool RegionComments::iterateDirectSubpaths(const DomItem &self, DirectVisitor visitor) const
+{
+ bool cont = true;
+ if (!m_regionComments.isEmpty()) {
+ cont = cont
+ && self.dvItemField(visitor, Fields::regionComments, [this, &self]() -> DomItem {
+ const Path pathFromOwner =
+ self.pathFromOwner().field(Fields::regionComments);
+ auto map = Map::fromFileRegionMap(pathFromOwner, m_regionComments);
+ return self.subMapItem(map);
+ });
+ }
+ return cont;
+}
+
+} // namespace Dom
+} // namespace QQmlJS
+QT_END_NAMESPACE
diff --git a/src/qmldom/qqmldomcomments_p.h b/src/qmldom/qqmldomcomments_p.h
new file mode 100644
index 0000000000..732a74162a
--- /dev/null
+++ b/src/qmldom/qqmldomcomments_p.h
@@ -0,0 +1,360 @@
+// Copyright (C) 2021 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+
+#ifndef QQMLDOMCOMMENTS_P_H
+#define QQMLDOMCOMMENTS_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_fwd_p.h"
+#include "qqmldomconstants_p.h"
+#include "qqmldomitem_p.h"
+#include "qqmldomattachedinfo_p.h"
+
+#include <QtQml/private/qqmljsast_p.h>
+#include <QtQml/private/qqmljsengine_p.h>
+
+#include <QtCore/QMultiMap>
+#include <QtCore/QHash>
+#include <QtCore/QStack>
+#include <QtCore/QCoreApplication>
+
+#include <memory>
+
+QT_BEGIN_NAMESPACE
+namespace QQmlJS {
+namespace Dom {
+
+class QMLDOM_EXPORT CommentInfo
+{
+ Q_DECLARE_TR_FUNCTIONS(CommentInfo)
+public:
+ CommentInfo(QStringView);
+
+ QStringView preWhitespace() const { return rawComment.mid(0, commentBegin); }
+
+ QStringView comment() const { return rawComment.mid(commentBegin, commentEnd - commentBegin); }
+
+ QStringView commentContent() const
+ {
+ return rawComment.mid(commentContentBegin, commentContentEnd - commentContentEnd);
+ }
+
+ QStringView postWhitespace() const
+ {
+ return rawComment.mid(commentEnd, rawComment.size() - commentEnd);
+ }
+
+ quint32 commentBegin = 0;
+ quint32 commentEnd = 0;
+ quint32 commentContentBegin = 0;
+ quint32 commentContentEnd = 0;
+ QStringView commentStartStr;
+ QStringView commentEndStr;
+ bool hasStartNewline = false;
+ bool hasEndNewline = false;
+ int nContentNewlines = 0;
+ QStringView rawComment;
+ QStringList warnings;
+};
+
+class QMLDOM_EXPORT Comment
+{
+public:
+ constexpr static DomType kindValue = DomType::Comment;
+ DomType kind() const { return kindValue; }
+
+ enum CommentType {Pre, Post};
+
+ Comment(const QString &c, const QQmlJS::SourceLocation &loc, int newlinesBefore = 1,
+ CommentType type = Pre)
+ : m_comment(c), m_location(loc), m_newlinesBefore(newlinesBefore), m_type(type)
+ {
+ }
+ Comment(QStringView c, const QQmlJS::SourceLocation &loc, int newlinesBefore = 1,
+ CommentType type = Pre)
+ : m_comment(c), m_location(loc), m_newlinesBefore(newlinesBefore), m_type(type)
+ {
+ }
+
+ bool iterateDirectSubpaths(const DomItem &self, DirectVisitor visitor) const;
+ int newlinesBefore() const { return m_newlinesBefore; }
+ void setNewlinesBefore(int n) { m_newlinesBefore = n; }
+ QStringView rawComment() const { return m_comment; }
+ CommentInfo info() const { return CommentInfo(m_comment); }
+ void write(OutWriter &lw, SourceLocation *commentLocation = nullptr) const;
+
+ CommentType type() const { return m_type; }
+
+ friend bool operator==(const Comment &c1, const Comment &c2)
+ {
+ return c1.m_newlinesBefore == c2.m_newlinesBefore && c1.m_comment == c2.m_comment;
+ }
+ friend bool operator!=(const Comment &c1, const Comment &c2) { return !(c1 == c2); }
+
+ QQmlJS::SourceLocation sourceLocation() const { return m_location; };
+
+private:
+ QStringView m_comment;
+ QQmlJS::SourceLocation m_location;
+ int m_newlinesBefore;
+ CommentType m_type;
+};
+
+class QMLDOM_EXPORT CommentedElement
+{
+public:
+ constexpr static DomType kindValue = DomType::CommentedElement;
+ DomType kind() const { return kindValue; }
+
+ bool iterateDirectSubpaths(const DomItem &self, DirectVisitor visitor) const;
+ void writePre(OutWriter &lw, QList<SourceLocation> *locations = nullptr) const;
+ void writePost(OutWriter &lw, QList<SourceLocation> *locations = nullptr) const;
+
+ friend bool operator==(const CommentedElement &c1, const CommentedElement &c2)
+ {
+ return c1.m_preComments == c2.m_preComments && c1.m_postComments == c2.m_postComments;
+ }
+ friend bool operator!=(const CommentedElement &c1, const CommentedElement &c2)
+ {
+ return !(c1 == c2);
+ }
+
+ void addComment(const Comment &comment)
+ {
+ if (comment.type() == Comment::CommentType::Pre)
+ m_preComments.append(comment);
+ else
+ m_postComments.append(comment);
+ }
+
+ const QList<Comment> &preComments() const { return m_preComments;}
+ const QList<Comment> &postComments() const { return m_postComments;}
+
+private:
+ QList<Comment> m_preComments;
+ QList<Comment> m_postComments;
+};
+
+class QMLDOM_EXPORT RegionComments
+{
+public:
+ constexpr static DomType kindValue = DomType::RegionComments;
+ DomType kind() const { return kindValue; }
+
+ bool iterateDirectSubpaths(const DomItem &self, DirectVisitor visitor) const;
+
+ friend bool operator==(const RegionComments &c1, const RegionComments &c2)
+ {
+ return c1.m_regionComments == c2.m_regionComments;
+ }
+ friend bool operator!=(const RegionComments &c1, const RegionComments &c2)
+ {
+ return !(c1 == c2);
+ }
+
+ const QMap<FileLocationRegion, CommentedElement> &regionComments() const { return m_regionComments;}
+ Path addComment(const Comment &comment, FileLocationRegion region)
+ {
+ if (comment.type() == Comment::CommentType::Pre)
+ return addPreComment(comment, region);
+ else
+ return addPostComment(comment, region);
+ }
+
+private:
+ Path addPreComment(const Comment &comment, FileLocationRegion region)
+ {
+ auto &preList = m_regionComments[region].preComments();
+ index_type idx = preList.size();
+ m_regionComments[region].addComment(comment);
+ return Path::Field(Fields::regionComments)
+ .key(fileLocationRegionName(region))
+ .field(Fields::preComments)
+ .index(idx);
+ }
+
+ Path addPostComment(const Comment &comment, FileLocationRegion region)
+ {
+ auto &postList = m_regionComments[region].postComments();
+ index_type idx = postList.size();
+ m_regionComments[region].addComment(comment);
+ return Path::Field(Fields::regionComments)
+ .key(fileLocationRegionName(region))
+ .field(Fields::postComments)
+ .index(idx);
+ }
+
+ QMap<FileLocationRegion, CommentedElement> m_regionComments;
+};
+
+class QMLDOM_EXPORT AstComments final : public OwningItem
+{
+protected:
+ std::shared_ptr<OwningItem> doCopy(const DomItem &) const override
+ {
+ return std::make_shared<AstComments>(*this);
+ }
+
+public:
+ constexpr static DomType kindValue = DomType::AstComments;
+ DomType kind() const override { return kindValue; }
+ bool iterateDirectSubpaths(const DomItem &self, DirectVisitor) const override;
+ std::shared_ptr<AstComments> makeCopy(const DomItem &self) const
+ {
+ return std::static_pointer_cast<AstComments>(doCopy(self));
+ }
+
+ Path canonicalPath(const DomItem &self) const override { return self.m_ownerPath; }
+ AstComments(const std::shared_ptr<Engine> &e) : m_engine(e) { }
+ AstComments(const AstComments &o)
+ : OwningItem(o), m_engine(o.m_engine), m_commentedElements(o.m_commentedElements)
+ {
+ }
+
+ const QHash<AST::Node *, CommentedElement> &commentedElements() const
+ {
+ return m_commentedElements;
+ }
+
+ QHash<AST::Node *, CommentedElement> &commentedElements()
+ {
+ return m_commentedElements;
+ }
+
+ CommentedElement *commentForNode(AST::Node *n)
+ {
+ if (m_commentedElements.contains(n))
+ return &(m_commentedElements[n]);
+ return nullptr;
+ }
+ QMultiMap<quint32, const QList<Comment> *> allCommentsInNode(AST::Node *n);
+
+private:
+ std::shared_ptr<Engine> m_engine;
+ QHash<AST::Node *, CommentedElement> m_commentedElements;
+};
+
+class CommentCollector
+{
+public:
+ CommentCollector() = default;
+ CommentCollector(MutableDomItem item);
+ void collectComments();
+ void collectComments(const std::shared_ptr<Engine> &engine, AST::Node *rootNode,
+ const std::shared_ptr<AstComments> &astComments);
+
+private:
+ MutableDomItem m_rootItem;
+ FileLocations::Tree m_fileLocations;
+};
+
+class VisitAll : public AST::Visitor
+{
+public:
+ VisitAll() = default;
+
+ static QSet<int> uiKinds();
+
+ void throwRecursionDepthError() override { }
+
+ bool visit(AST::UiPublicMember *el) override
+ {
+ AST::Node::accept(el->annotations, this);
+ AST::Node::accept(el->memberType, this);
+ return true;
+ }
+
+ bool visit(AST::UiSourceElement *el) override
+ {
+ AST::Node::accept(el->annotations, this);
+ return true;
+ }
+
+ bool visit(AST::UiObjectDefinition *el) override
+ {
+ AST::Node::accept(el->annotations, this);
+ return true;
+ }
+
+ bool visit(AST::UiObjectBinding *el) override
+ {
+ AST::Node::accept(el->annotations, this);
+ return true;
+ }
+
+ bool visit(AST::UiScriptBinding *el) override
+ {
+ AST::Node::accept(el->annotations, this);
+ return true;
+ }
+
+ bool visit(AST::UiArrayBinding *el) override
+ {
+ AST::Node::accept(el->annotations, this);
+ return true;
+ }
+
+ bool visit(AST::UiParameterList *el) override
+ {
+ AST::Node::accept(el->type, this);
+ return true;
+ }
+
+ bool visit(AST::UiQualifiedId *el) override
+ {
+ AST::Node::accept(el->next, this);
+ return true;
+ }
+
+ bool visit(AST::UiEnumDeclaration *el) override
+ {
+ AST::Node::accept(el->annotations, this);
+ return true;
+ }
+
+ bool visit(AST::UiInlineComponent *el) override
+ {
+ AST::Node::accept(el->annotations, this);
+ return true;
+ }
+
+ void endVisit(AST::UiImport *el) override { AST::Node::accept(el->version, this); }
+ void endVisit(AST::UiPublicMember *el) override { AST::Node::accept(el->parameters, this); }
+
+ void endVisit(AST::UiParameterList *el) override
+ {
+ AST::Node::accept(el->next, this); // put other args at the same level as this one...
+ }
+
+ void endVisit(AST::UiEnumMemberList *el) override
+ {
+ AST::Node::accept(el->next,
+ this); // put other enum members at the same level as this one...
+ }
+
+ bool visit(AST::TemplateLiteral *el) override
+ {
+ AST::Node::accept(el->expression, this);
+ return true;
+ }
+
+ void endVisit(AST::Elision *el) override
+ {
+ AST::Node::accept(el->next, this); // emit other elisions at the same level
+ }
+};
+} // namespace Dom
+} // namespace QQmlJS
+QT_END_NAMESPACE
+
+#endif // QQMLDOMCOMMENTS_P_H
diff --git a/src/qmldom/qqmldomcompare.cpp b/src/qmldom/qqmldomcompare.cpp
new file mode 100644
index 0000000000..65b357cc75
--- /dev/null
+++ b/src/qmldom/qqmldomcompare.cpp
@@ -0,0 +1,232 @@
+// Copyright (C) 2020 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+#include "qqmldomcompare_p.h"
+#include "QtCore/qglobal.h"
+
+QT_BEGIN_NAMESPACE
+
+namespace QQmlJS {
+namespace Dom {
+
+bool domCompare(const DomItem &i1, const DomItem &i2, function_ref<bool(Path, const DomItem &, const DomItem &)> change,
+ function_ref<bool(const DomItem &, const PathEls::PathComponent &, const 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;
+ case DomKind::ScriptElement: {
+ // TODO: implement me
+ return false;
+
+ } break;
+ }
+ }
+ return true;
+}
+
+QStringList
+domCompareStrList(const DomItem &i1, const DomItem &i2,
+ function_ref<bool(const DomItem &, const PathEls::PathComponent &, const DomItem &) const> filter,
+ DomCompareStrList stopAtFirstDiff)
+{
+ QStringList res;
+ bool hasDiff = false;
+ domCompare(
+ i1, i2,
+ [&res, &hasDiff, stopAtFirstDiff](const Path &p, const DomItem &j1, const 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;
+ case DomKind::ScriptElement: {
+ // implement me
+ 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..651486d7a9
--- /dev/null
+++ b/src/qmldom/qqmldomcompare_p.h
@@ -0,0 +1,72 @@
+// Copyright (C) 2020 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+
+#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(
+ const DomItem &i1, const DomItem &i2, function_ref<bool(Path, const DomItem &, const DomItem &)> change,
+ function_ref<bool(const DomItem &, const PathEls::PathComponent &, const DomItem &)> filter = noFilter,
+ Path p = Path());
+
+enum DomCompareStrList { FirstDiff, AllDiffs };
+
+QMLDOM_EXPORT QStringList domCompareStrList(
+ const DomItem &i1, const DomItem &i2,
+ function_ref<bool(const DomItem &, const PathEls::PathComponent &, const DomItem &) const> filter = noFilter,
+ DomCompareStrList stopAtFirstDiff = DomCompareStrList::FirstDiff);
+
+inline QStringList domCompareStrList(
+ MutableDomItem &i1, const DomItem &i2,
+ function_ref<bool(const DomItem &, const PathEls::PathComponent &, const DomItem &) const> filter = noFilter,
+ DomCompareStrList stopAtFirstDiff = DomCompareStrList::FirstDiff)
+{
+ DomItem ii1 = i1.item();
+ return domCompareStrList(ii1, i2, filter, stopAtFirstDiff);
+}
+
+inline QStringList domCompareStrList(
+ const DomItem &i1, MutableDomItem &i2,
+ function_ref<bool(const DomItem &, const PathEls::PathComponent &, const DomItem &) const> 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(const DomItem &, const PathEls::PathComponent &, const DomItem &) const> 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/qqmldomconstants.cpp b/src/qmldom/qqmldomconstants.cpp
new file mode 100644
index 0000000000..e6068fdcbb
--- /dev/null
+++ b/src/qmldom/qqmldomconstants.cpp
@@ -0,0 +1,13 @@
+// Copyright (C) 2022 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+#include "qqmldomconstants_p.h"
+
+QT_BEGIN_NAMESPACE
+
+namespace QQmlJS::Dom {
+
+} // end namespace QQmlJS::Dom
+
+QT_END_NAMESPACE
+
+#include "moc_qqmldomconstants_p.cpp"
diff --git a/src/qmldom/qqmldomconstants_p.h b/src/qmldom/qqmldomconstants_p.h
index 3fcd4a639b..1ca9389546 100644
--- a/src/qmldom/qqmldomconstants_p.h
+++ b/src/qmldom/qqmldomconstants_p.h
@@ -1,40 +1,6 @@
-/****************************************************************************
-**
-** 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$
-**/
+// Copyright (C) 2020 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+
#ifndef QQMLDOMCONSTANTS_P_H
#define QQMLDOMCONSTANTS_P_H
@@ -53,6 +19,7 @@
#include <QtCore/QObject>
#include <QtCore/QMetaObject>
+#include <QtCore/private/qglobal_p.h>
QT_BEGIN_NAMESPACE
@@ -86,6 +53,10 @@ enum class PathCurrent {
Lookup
};
Q_ENUM_NS(PathCurrent)
+
+enum class Language { QmlQuick1, QmlQuick2, QmlQuick3, QmlCompiled, QmlAnnotation, Qbs };
+Q_ENUM_NS(Language)
+
enum class ResolveOption{
None=0,
TraceVisit=0x1 // call the function along all elements of the path, not just for the target (the function might be called even if the target is never reached)
@@ -94,49 +65,74 @@ Q_ENUM_NS(ResolveOption)
Q_DECLARE_FLAGS(ResolveOptions, ResolveOption)
Q_DECLARE_OPERATORS_FOR_FLAGS(ResolveOptions)
-enum class VisitOption{
- None=0,
- VisitAdopted=0x1, // Visit adopted types (but never recurses)
- Recurse=0x2, // recurse non adopted types
- NoPath=0x4 // does not generate path consistent with visit
+enum class VisitOption {
+ None = 0,
+ VisitSelf = 0x1, // Visit the start item
+ VisitAdopted = 0x2, // Visit adopted types (but never recurses them)
+ Recurse = 0x4, // recurse non adopted types
+ NoPath = 0x8, // does not generate path consistent with visit
+ Default = VisitOption::VisitSelf | VisitOption::VisitAdopted | VisitOption::Recurse
};
Q_ENUM_NS(VisitOption)
Q_DECLARE_FLAGS(VisitOptions, VisitOption)
Q_DECLARE_OPERATORS_FOR_FLAGS(VisitOptions)
-enum class DomKind {
- Empty,
- Object,
- List,
- Map,
- Value
+enum class LookupOption {
+ Normal = 0,
+ Strict = 0x1,
+ VisitTopClassType = 0x2, // static lookup of class (singleton) or attached type, the default is
+ // visiting instance methods
+ SkipFirstScope = 0x4
};
+Q_ENUM_NS(LookupOption)
+Q_DECLARE_FLAGS(LookupOptions, LookupOption)
+Q_DECLARE_OPERATORS_FOR_FLAGS(LookupOptions)
+
+enum class LookupType { PropertyDef, Binding, Property, Method, Type, CppType, Symbol };
+Q_ENUM_NS(LookupType)
+
+enum class VisitPrototypesOption {
+ Normal = 0,
+ SkipFirst = 0x1,
+ RevisitWarn = 0x2,
+ ManualProceedToScope = 0x4
+};
+Q_ENUM_NS(VisitPrototypesOption)
+Q_DECLARE_FLAGS(VisitPrototypesOptions, VisitPrototypesOption)
+Q_DECLARE_OPERATORS_FOR_FLAGS(VisitPrototypesOptions)
+
+enum class DomKind { Empty, Object, List, Map, Value, ScriptElement };
Q_ENUM_NS(DomKind)
enum class DomType {
- Empty,
+ Empty, // only for default ctor
- ExternalItemInfo,
- ExternalItemPair,
+ ExternalItemInfo, // base class for anything represented by an actual file
+ ExternalItemPair, // pair of newest version of item, and latest valid update ### REVISIT
// ExternalOwningItems refer to an external path and can be shared between environments
- QmlDirectory, // dir
+ QmlDirectory, // dir e.g. used for implicit import
QmldirFile, // qmldir
JsFile, // file
QmlFile, // file
QmltypesFile, // qmltypes
- GlobalScope, // language dependent
+ GlobalScope, // language dependent (currently no difference)
+ /* enum A { B, C }
+ * *
+ EnumItem is marked with * */
EnumItem,
// types
- EnumDecl,
- JsResource,
- QmltypesComponent,
- QmlComponent,
- GlobalComponent,
+ EnumDecl, // A in above example
+ JsResource, // QML file contains QML object, JSFile contains JsResource
+ QmltypesComponent, // Component inside a qmltypes fles; compared to component it has exported
+ // meta-object revisions; singleton flag; can export multiple names
+ QmlComponent, // "normal" QML file based Component; also can represent inline components
+ GlobalComponent, // component of global object ### REVISIT, try to replace with one of the above
ModuleAutoExport, // dependent imports to automatically load when a module is imported
ModuleIndex, // index for all the imports of a major version
ModuleScope, // a specific import with full version
+ ImportScope, // the scope including the types coming from one or more imports
Export, // An exported type
// header stuff
@@ -145,49 +141,114 @@ enum class DomType {
// qml elements
Id,
- QmlObject,
- ConstantData,
- SimpleObjectWrap,
- ScriptExpression,
- Reference,
- Binding,
- PropertyDefinition,
- RequiredProperty,
+ QmlObject, // the Item in Item {}; also used to represent types in qmltype files
+ ConstantData, // the 2 in "property int i: 2"; can be any generic data in a QML document
+ SimpleObjectWrap, // internal wrapping to give uniform DOMItem access; ### research more
+ ScriptExpression, // wraps an AST script expression as a DOMItem
+ Reference, // reference to another DOMItem; e.g. asking for a type of an object returns a
+ // Reference
+ PropertyDefinition, // _just_ the property definition; without the binding, even if it's one
+ // line
+ Binding, // the part after the ":"
MethodParameter,
- MethodInfo,
+ MethodInfo, // container of MethodParameter
Version, // wrapped
+ Comment,
+ CommentedElement, // attached to AST if they have pre-/post-comments?
+ RegionComments, // DomItems have attached RegionComments; can attach comments to fine grained
+ // "regions" in a DomItem; like the default keyword of a property definition
+ AstComments, // hash-table from AST node to commented element
+ FileLocations, // mapping from DomItem to file location ### REVISIT: try to move out of
+ // hierarchy?
+ UpdatedScriptExpression, // used in writeOut method when formatting changes ### Revisit: try to
+ // move out of DOM hierarchy
- // generic objects, mainly for debug support
- GenericObject,
- GenericOwner,
+ // convenience collecting types
+ PropertyInfo, // not a DOM Item, just a convenience class
+
+ // Moc objects, mainly for testing ### Try to remove them; replace their usage in tests with
+ // "real" instances
+ MockObject,
+ MockOwner,
// containers
Map,
List,
+ ListP,
// supporting objects
- LoadInfo, // owning
+ LoadInfo, // owning, used inside DomEnvironment ### REVISIT: move out of hierarchy
ErrorMessage, // wrapped
+ AttachedInfo, // owning
// Dom top level
- DomEnvironment,
- DomUniverse
+ DomEnvironment, // a consistent view of modules, types, files, etc.
+ DomUniverse, // a cache of what can be found in the DomEnvironment, contains the latest valid
+ // version for every file/type, etc. + latest overall
+
+ // Dom Script elements
+ // TODO
+ ScriptElementWrap, // internal wrapping to give uniform access of script elements (e.g. for
+ // statement lists)
+ ScriptElementStart, // marker to check if a DomType is a scriptelement or not
+ ScriptBlockStatement = ScriptElementStart,
+ ScriptIdentifierExpression,
+ ScriptLiteral,
+ ScriptForStatement,
+ ScriptIfStatement,
+ ScriptPostExpression,
+ ScriptUnaryExpression,
+ ScriptBinaryExpression,
+ ScriptVariableDeclaration,
+ ScriptVariableDeclarationEntry,
+ ScriptReturnStatement,
+ ScriptGenericElement,
+ ScriptCallExpression,
+ ScriptFormalParameter,
+ ScriptArray,
+ ScriptObject,
+ ScriptProperty,
+ ScriptType,
+ ScriptElision,
+ ScriptArrayEntry,
+ ScriptPattern,
+ ScriptSwitchStatement,
+ ScriptCaseBlock,
+ ScriptCaseClause,
+ ScriptDefaultClause,
+ ScriptWhileStatement,
+ ScriptDoWhileStatement,
+ ScriptForEachStatement,
+ ScriptTryCatchStatement,
+ ScriptThrowStatement,
+ ScriptLabelledStatement,
+ ScriptBreakStatement,
+ ScriptContinueStatement,
+ ScriptConditionalExpression,
+ ScriptEmptyStatement,
+ ScriptParenthesizedExpression,
+
+ ScriptElementStop, // marker to check if a DomType is a scriptelement or not
};
Q_ENUM_NS(DomType)
+enum class SimpleWrapOption { None = 0, ValueType = 1 };
+Q_ENUM_NS(SimpleWrapOption)
+Q_DECLARE_FLAGS(SimpleWrapOptions, SimpleWrapOption)
+Q_DECLARE_OPERATORS_FOR_FLAGS(SimpleWrapOptions)
+
+enum class BindingValueKind { Object, ScriptExpression, Array, Empty };
+Q_ENUM_NS(BindingValueKind)
+
+enum class BindingType { Normal, OnBinding };
+Q_ENUM_NS(BindingType)
+
enum class ListOptions {
Normal,
Reverse
};
Q_ENUM_NS(ListOptions)
-enum class LoadOption {
- DefaultLoad = 0x0,
- ForceLoad = 0x1,
-};
-Q_ENUM_NS(LoadOption)
-Q_DECLARE_FLAGS(LoadOptions, LoadOption);
-
enum class EscapeOptions{
OuterQuotes,
NoOuterQuotes
@@ -195,17 +256,154 @@ enum class EscapeOptions{
Q_ENUM_NS(EscapeOptions)
enum class ErrorLevel{
- Debug,
- Info,
- Hint, // cosmetic or convention
- MaybeWarning, // possibly a warning, insufficient information
- Warning,
- MaybeError,
- Error,
- Fatal
+ Debug = QtMsgType::QtDebugMsg,
+ Info = QtMsgType::QtInfoMsg,
+ Warning = QtMsgType::QtWarningMsg,
+ Error = QtMsgType::QtCriticalMsg,
+ Fatal = QtMsgType::QtFatalMsg
};
Q_ENUM_NS(ErrorLevel)
+enum class AstDumperOption {
+ None=0,
+ NoLocations=0x1,
+ NoAnnotations=0x2,
+ DumpNode=0x4,
+ SloppyCompare=0x8
+};
+Q_ENUM_NS(AstDumperOption)
+Q_DECLARE_FLAGS(AstDumperOptions, AstDumperOption)
+Q_DECLARE_OPERATORS_FOR_FLAGS(AstDumperOptions)
+
+enum class GoTo {
+ Strict, // never go to an non uniquely defined result
+ MostLikely // if needed go up to the most likely location between multiple options
+};
+Q_ENUM_NS(GoTo)
+
+enum class AddOption { KeepExisting, Overwrite };
+Q_ENUM_NS(AddOption)
+
+/*!
+\internal
+FilterUpOptions decide in which direction the filtering is done.
+ReturnInner starts the search at top(), and work its way down to the current
+element.
+ReturnOuter and ReturnOuterNoSelf starts the search at the current element and
+works their way up to to top().
+*/
+enum class FilterUpOptions { ReturnOuter, ReturnOuterNoSelf, ReturnInner };
+Q_ENUM_NS(FilterUpOptions)
+
+enum class WriteOutCheck {
+ None = 0x0,
+ UpdatedDomCompare = 0x1,
+ UpdatedDomStable = 0x2,
+ Reparse = 0x4,
+ ReparseCompare = 0x8,
+ ReparseStable = 0x10,
+ DumpOnFailure = 0x20,
+ All = 0x3F,
+ Default = Reparse | ReparseCompare | ReparseStable
+};
+Q_ENUM_NS(WriteOutCheck)
+Q_DECLARE_FLAGS(WriteOutChecks, WriteOutCheck)
+Q_DECLARE_OPERATORS_FOR_FLAGS(WriteOutChecks)
+
+enum class LocalSymbolsType {
+ None = 0x0,
+ ObjectType = 0x1,
+ ValueType = 0x2,
+ Signal = 0x4,
+ Method = 0x8,
+ Attribute = 0x10,
+ Id = 0x20,
+ Namespace = 0x40,
+ Global = 0x80,
+ MethodParameter = 0x100,
+ Singleton = 0x200,
+ AttachedType = 0x400,
+};
+Q_ENUM_NS(LocalSymbolsType)
+Q_DECLARE_FLAGS(LocalSymbolsTypes, LocalSymbolsType)
+Q_DECLARE_OPERATORS_FOR_FLAGS(LocalSymbolsTypes)
+
+/*!
+\internal
+The FileLocationRegion allows to map the different FileLocation subregions to their position in
+the actual code. For example, \c{ColonTokenRegion} denotes the position of the ':' token in a
+binding like `myProperty: something()`, or the ':' token in a pragma like `pragma Hello: World`.
+
+These are used for formatting in qmlformat and autocompletion in qmlls.
+
+MainRegion denotes the entire FileLocation region.
+
+\sa{OutWriter::regionToString}, {FileLocations::regionName}
+*/
+enum FileLocationRegion : int {
+ AsTokenRegion,
+ BreakKeywordRegion,
+ DoKeywordRegion,
+ CaseKeywordRegion,
+ CatchKeywordRegion,
+ ColonTokenRegion,
+ CommaTokenRegion,
+ ComponentKeywordRegion,
+ ContinueKeywordRegion,
+ DefaultKeywordRegion,
+ EllipsisTokenRegion,
+ ElseKeywordRegion,
+ EnumKeywordRegion,
+ EnumValueRegion,
+ EqualTokenRegion,
+ ForKeywordRegion,
+ FinallyKeywordRegion,
+ FirstSemicolonTokenRegion,
+ FunctionKeywordRegion,
+ IdColonTokenRegion,
+ IdNameRegion,
+ IdTokenRegion,
+ IdentifierRegion,
+ ImportTokenRegion,
+ ImportUriRegion,
+ InOfTokenRegion,
+ LeftBraceRegion,
+ LeftBracketRegion,
+ LeftParenthesisRegion,
+ MainRegion,
+ OperatorTokenRegion,
+ OnTargetRegion,
+ OnTokenRegion,
+ PragmaKeywordRegion,
+ PragmaValuesRegion,
+ PropertyKeywordRegion,
+ QuestionMarkTokenRegion,
+ ReadonlyKeywordRegion,
+ RequiredKeywordRegion,
+ ReturnKeywordRegion,
+ RightBraceRegion,
+ RightBracketRegion,
+ RightParenthesisRegion,
+ SecondSemicolonRegion,
+ SemicolonTokenRegion,
+ SignalKeywordRegion,
+ ThrowKeywordRegion,
+ TryKeywordRegion,
+ TypeIdentifierRegion,
+ VersionRegion,
+ WhileKeywordRegion,
+};
+Q_ENUM_NS(FileLocationRegion);
+
+enum DomCreationOption : char {
+ None = 0,
+ WithSemanticAnalysis = 1,
+ WithScriptExpressions = 2,
+ WithRecovery = 4
+};
+
+Q_DECLARE_FLAGS(DomCreationOptions, DomCreationOption);
+
} // end namespace Dom
} // end namespace QQmlJS
diff --git a/src/qmldom/qqmldomelements.cpp b/src/qmldom/qqmldomelements.cpp
new file mode 100644
index 0000000000..0fe5c3eb36
--- /dev/null
+++ b/src/qmldom/qqmldomelements.cpp
@@ -0,0 +1,2196 @@
+// Copyright (C) 2021 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+// Suppress GCC 11 warning about maybe-uninitialized copy of
+// another Data. We're not sure if the compiler is actually right,
+// but in this type of warning, it often isn't.
+//#if defined(Q_CC_GNU) && Q_CC_GNU >= 1100
+//QT_WARNING_DISABLE_GCC("-Wmaybe-uninitialized")
+#include "qqmldomconstants_p.h"
+#include "qqmldompath_p.h"
+#if defined(__GNUC__) && __GNUC__ >= 11
+# pragma GCC diagnostic ignored "-Wmaybe-uninitialized"
+#endif
+
+#include "qqmldomelements_p.h"
+#include "qqmldomcomments_p.h"
+#include "qqmldomastdumper_p.h"
+#include "qqmldommock_p.h"
+#include "qqmldomreformatter_p.h"
+#include "qqmldomoutwriter_p.h"
+#include "qqmldomlinewriter_p.h"
+#include "qqmldomtop_p.h"
+#include "qqmldomexternalitems_p.h"
+
+#include <QtQml/private/qqmljslexer_p.h>
+#include <QtQml/private/qqmljsparser_p.h>
+#include <QtQml/private/qqmljsengine_p.h>
+#include <QtQml/private/qqmljsastvisitor_p.h>
+#include <QtQml/private/qqmljsast_p.h>
+
+#include <QtCore/QScopeGuard>
+#include <QtCore/QRegularExpression>
+#include <QtCore/QDir>
+#include <QtCore/QBasicMutex>
+#include <QtCore/QUrl>
+
+#include <optional>
+#include <limits>
+
+QT_BEGIN_NAMESPACE
+
+using namespace Qt::StringLiterals;
+
+namespace QQmlJS {
+namespace Dom {
+
+namespace Paths {
+
+Path moduleIndexPath(const QString &uri, int majorVersion, const ErrorHandler &errorHandler)
+{
+ QString version = QString::number(majorVersion);
+ if (majorVersion == Version::Latest)
+ version = QLatin1String("Latest");
+ else if (majorVersion == Version::Undefined)
+ version = QString();
+ QRegularExpression moduleRe(QLatin1String(R"(\A\w+(?:\.\w+)*\Z)"));
+ auto m = moduleRe.match(uri);
+ if (!m.isValid())
+ Path::myErrors()
+ .error(Path::tr("Invalid module name in import %1").arg(uri))
+ .handle(errorHandler);
+ return Path::Root(PathRoot::Env).field(Fields::moduleIndexWithUri).key(uri).key(version);
+}
+
+Path moduleScopePath(const QString &uri, Version version, const ErrorHandler &)
+{
+ return Path::Root(PathRoot::Env)
+ .field(Fields::moduleIndexWithUri)
+ .key(uri)
+ .key(version.majorSymbolicString())
+ .field(Fields::moduleScope)
+ .key(version.minorString());
+}
+
+Path moduleScopePath(const QString &uri, const QString &version, const ErrorHandler &errorHandler)
+{
+ Version v = Version::fromString(version);
+ if (!version.isEmpty() && !(v.isValid() || v.isLatest()))
+ Path::myErrors().error(Path::tr("Invalid Version %1").arg(version)).handle(errorHandler);
+ return moduleScopePath(uri, v, errorHandler);
+}
+
+} // end namespace Paths
+
+static ErrorGroups domParsingErrors()
+{
+ static ErrorGroups res = { { DomItem::domErrorGroup, NewErrorGroup("Parsing") } };
+ return res;
+}
+
+bool CommentableDomElement::iterateDirectSubpaths(const DomItem &self, DirectVisitor visitor) const
+{
+ bool cont = true;
+ cont = cont && self.dvWrapField(visitor, Fields::comments, m_comments);
+ return cont;
+}
+
+void Component::updatePathFromOwner(const Path &newPath)
+{
+ DomElement::updatePathFromOwner(newPath);
+ updatePathFromOwnerMultiMap(m_enumerations, newPath.field(Fields::enumerations));
+ updatePathFromOwnerQList(m_objects, newPath.field(Fields::objects));
+}
+
+Component::Component(const QString &name) : CommentableDomElement(Path()), m_name(name) { }
+
+Component::Component(const Path &pathFromOwner) : CommentableDomElement(pathFromOwner) { }
+
+bool Component::iterateDirectSubpaths(const DomItem &self, DirectVisitor visitor) const
+{
+ bool cont = CommentableDomElement::iterateDirectSubpaths(self, visitor);
+ cont = cont && self.dvValueField(visitor, Fields::name, name());
+ cont = cont && self.dvWrapField(visitor, Fields::enumerations, m_enumerations);
+ cont = cont && self.dvWrapField(visitor, Fields::objects, m_objects);
+ cont = cont && self.dvValueField(visitor, Fields::isSingleton, isSingleton());
+ cont = cont && self.dvValueField(visitor, Fields::isCreatable, isCreatable());
+ cont = cont && self.dvValueField(visitor, Fields::isComposite, isComposite());
+ cont = cont && self.dvValueField(visitor, Fields::attachedTypeName, attachedTypeName());
+ cont = cont && self.dvReferenceField(visitor, Fields::attachedType, attachedTypePath(self));
+ return cont;
+}
+
+DomItem Component::field(const DomItem &self, QStringView name) const
+{
+ if (name == Fields::name)
+ return self.wrapField(Fields::name, m_name);
+ if (name == Fields::objects)
+ return self.wrapField(Fields::objects, m_objects);
+
+ return DomBase::field(self, name);
+}
+
+Path Component::addObject(const QmlObject &object, QmlObject **oPtr)
+{
+ return appendUpdatableElementInQList(pathFromOwner().field(Fields::objects), m_objects, object,
+ oPtr);
+}
+
+bool QmlComponent::iterateDirectSubpaths(const DomItem &self, DirectVisitor visitor) const
+{
+ bool cont = Component::iterateDirectSubpaths(self, visitor);
+ cont = cont && self.dvWrapField(visitor, Fields::ids, m_ids);
+ cont = cont && self.dvValueLazyField(visitor, Fields::subComponents, [this, &self]() {
+ return this->subComponents(self);
+ });
+ if (m_nameIdentifiers) {
+ cont = cont && self.dvItemField(visitor, Fields::nameIdentifiers, [this, &self]() {
+ return self.subScriptElementWrapperItem(m_nameIdentifiers);
+ });
+ }
+ return cont;
+}
+
+void QmlComponent::updatePathFromOwner(const Path &newPath)
+{
+ Component::updatePathFromOwner(newPath);
+ updatePathFromOwnerMultiMap(m_ids, newPath.field(Fields::annotations));
+}
+
+void QmlComponent::writeOut(const DomItem &self, OutWriter &lw) const
+{
+ if (name().contains(QLatin1Char('.'))) {
+ // inline component
+ lw.ensureNewline()
+ .writeRegion(ComponentKeywordRegion)
+ .space()
+ .writeRegion(IdentifierRegion, name().split(QLatin1Char('.')).last())
+ .writeRegion(ColonTokenRegion)
+ .space();
+ }
+ self.field(Fields::objects).index(0).writeOut(lw);
+}
+
+QList<QString> QmlComponent::subComponentsNames(const DomItem &self) const
+{
+ DomItem components = self.owner().field(Fields::components);
+ const QSet<QString> cNames = components.keys();
+ QString myNameDot = self.pathFromOwner()[1].headName();
+ if (!myNameDot.isEmpty())
+ myNameDot += QLatin1Char('.');
+ QList<QString> subNames;
+ for (const QString &cName : cNames)
+ if (cName.startsWith(myNameDot)
+ && !QStringView(cName).mid(myNameDot.size()).contains(QLatin1Char('.'))
+ && !cName.isEmpty())
+ subNames.append(cName);
+ std::sort(subNames.begin(), subNames.end());
+ return subNames;
+}
+
+QList<DomItem> QmlComponent::subComponents(const DomItem &self) const
+{
+ DomItem components = self.owner().field(Fields::components);
+ QList<DomItem> res;
+ for (const QString &cName : subComponentsNames(self))
+ for (const DomItem &comp : components.key(cName).values())
+ res.append(comp);
+ return res;
+}
+
+Version Version::fromString(QStringView v)
+{
+ if (v.isEmpty())
+ return Version(Latest, Latest);
+ QRegularExpression r(
+ QRegularExpression::anchoredPattern(QStringLiteral(uR"(([0-9]*)(?:\.([0-9]*))?)")));
+ auto m = r.matchView(v);
+ if (m.hasMatch()) {
+ bool ok;
+ int majorV = m.capturedView(1).toInt(&ok);
+ if (!ok)
+ majorV = Version::Undefined;
+ int minorV = m.capturedView(2).toInt(&ok);
+ if (!ok)
+ minorV = Version::Undefined;
+ return Version(majorV, minorV);
+ }
+ return {};
+}
+
+Version::Version(qint32 majorV, qint32 minorV) : majorVersion(majorV), minorVersion(minorV) { }
+
+bool Version::isLatest() const
+{
+ return majorVersion == Latest && minorVersion == Latest;
+}
+
+bool Version::isValid() const
+{
+ return majorVersion >= 0 && minorVersion >= 0;
+}
+
+QString Version::stringValue() const
+{
+ if (isLatest())
+ return QString();
+ if (minorVersion < 0) {
+ if (majorVersion < 0)
+ return QLatin1String(".");
+ else
+ return QString::number(majorVersion);
+ }
+ if (majorVersion < 0)
+ return QLatin1String(".") + QString::number(minorVersion);
+ return QString::number(majorVersion) + QChar::fromLatin1('.') + QString::number(minorVersion);
+}
+
+bool Version::iterateDirectSubpaths(const DomItem &self, DirectVisitor visitor) const
+{
+ bool cont = true;
+ cont = cont && self.dvWrapField(visitor, Fields::majorVersion, majorVersion);
+ cont = cont && self.dvWrapField(visitor, Fields::minorVersion, minorVersion);
+ cont = cont && self.dvValueField(visitor, Fields::isLatest, isLatest());
+ cont = cont && self.dvValueField(visitor, Fields::isValid, isValid());
+ cont = cont && self.dvValueLazyField(visitor, Fields::stringValue, [this]() {
+ return this->stringValue();
+ });
+ return cont;
+}
+
+QRegularExpression Import::importRe()
+{
+ static QRegularExpression res(QRegularExpression::anchoredPattern(QStringLiteral(
+ uR"((?<uri>\w+(?:\.\w+)*)(?:\W+(?<version>[0-9]+(?:\.[0-9]*)?))?(?:\W+as\W+(?<id>\w+))?$)")));
+ return res;
+}
+
+Import Import::fromUriString(
+ const QString &importStr, Version v, const QString &importId, const ErrorHandler &handler)
+{
+ auto m = importRe().match(importStr);
+ if (m.hasMatch()) {
+ if (v.majorVersion == Version::Undefined && v.minorVersion == Version::Undefined)
+ v = Version::fromString(m.captured(2));
+ else if (!m.captured(u"version").isEmpty())
+ domParsingErrors()
+ .warning(tr("Version %1 in import string '%2' overridden by explicit "
+ "version %3")
+ .arg(m.captured(2), importStr, v.stringValue()))
+ .handle(handler);
+ QString resolvedImportId;
+ if (importId.isEmpty()) {
+ resolvedImportId = m.captured(u"importId");
+ } else {
+ if (!m.captured(u"importId").isEmpty()) {
+ domParsingErrors()
+ .warning(tr("namespace %1 in import string '%2' overridden by explicit "
+ "importId %3")
+ .arg(m.captured(u"importId"), importStr, importId))
+ .handle(handler);
+ }
+ resolvedImportId = importId;
+ }
+
+ return Import(QmlUri::fromUriString(m.captured(u"uri").trimmed()), v, resolvedImportId);
+ }
+ domParsingErrors()
+ .error(tr("Unexpected URI format in import '%1'").arg(importStr))
+ .handle(handler);
+ return Import();
+}
+
+Import Import::fromFileString(
+ const QString &importStr, const QString &importId, const ErrorHandler &)
+{
+ return Import(QmlUri::fromDirectoryString(importStr), Version(), importId);
+}
+
+bool Import::iterateDirectSubpaths(const DomItem &self, DirectVisitor visitor) const
+{
+ bool cont = true;
+ cont = cont && self.dvValueField(visitor, Fields::uri, uri.toString());
+ cont = cont && self.dvWrapField(visitor, Fields::version, version);
+ if (!importId.isEmpty())
+ cont = cont && self.dvValueField(visitor, Fields::importId, importId);
+ if (implicit)
+ cont = cont && self.dvValueField(visitor, Fields::implicit, implicit);
+ cont = cont && self.dvWrapField(visitor, Fields::comments, comments);
+ return cont;
+}
+
+void Import::writeOut(const DomItem &self, OutWriter &ow) const
+{
+ if (implicit)
+ return;
+
+ QString code;
+ const DomItem owner = self.owner();
+ if (std::shared_ptr<QmlFile> qmlFilePtr = self.ownerAs<QmlFile>())
+ code = qmlFilePtr->code();
+
+ // check for an empty line before the import, and preserve it
+ int preNewlines = 0;
+
+ const FileLocations::Tree elLoc = FileLocations::findAttachedInfo(self).foundTree;
+
+ quint32 start = elLoc->info().fullRegion.offset;
+ if (size_t(code.size()) >= start) {
+ while (start != 0) {
+ QChar c = code.at(--start);
+ if (c == u'\n') {
+ if (++preNewlines == 2)
+ break;
+ } else if (!c.isSpace())
+ break;
+ }
+ }
+ if (preNewlines == 0)
+ ++preNewlines;
+
+ ow.ensureNewline(preNewlines);
+ ow.writeRegion(ImportTokenRegion).space();
+ ow.writeRegion(ImportUriRegion, uri.toString());
+ if (uri.isModule()) {
+ QString vString = version.stringValue();
+ if (!vString.isEmpty())
+ ow.space().write(vString);
+ }
+ if (!importId.isEmpty())
+ ow.space().writeRegion(AsTokenRegion).space().writeRegion(IdNameRegion, importId);
+}
+
+Id::Id(const QString &idName, const Path &referredObject) : name(idName), referredObjectPath(referredObject) { }
+
+bool Id::iterateDirectSubpaths(const DomItem &self, DirectVisitor visitor) const
+{
+ bool cont = true;
+ cont = cont && self.dvValueField(visitor, Fields::name, name);
+ cont = cont && self.dvReferenceField(visitor, Fields::referredObject, referredObjectPath);
+ cont = cont && self.dvWrapField(visitor, Fields::comments, comments);
+ cont = cont && self.dvWrapField(visitor, Fields::annotations, annotations);
+ cont = cont && self.dvWrapField(visitor, Fields::value, value);
+ return cont;
+}
+
+void Id::updatePathFromOwner(const Path &newPath)
+{
+ updatePathFromOwnerQList(annotations, newPath.field(Fields::annotations));
+}
+
+Path Id::addAnnotation(const Path &selfPathFromOwner, const QmlObject &annotation, QmlObject **aPtr)
+{
+ return appendUpdatableElementInQList(selfPathFromOwner.field(Fields::annotations), annotations,
+ annotation, aPtr);
+}
+
+QmlObject::QmlObject(const Path &pathFromOwner) : CommentableDomElement(pathFromOwner) { }
+
+bool QmlObject::iterateBaseDirectSubpaths(const DomItem &self, DirectVisitor visitor) const
+{
+ bool cont = CommentableDomElement::iterateDirectSubpaths(self, visitor);
+ if (!idStr().isEmpty())
+ cont = cont && self.dvValueField(visitor, Fields::idStr, idStr());
+ cont = cont && self.dvValueField(visitor, Fields::name, name());
+ if (!prototypePaths().isEmpty())
+ cont = cont && self.dvReferencesField(visitor, Fields::prototypes, m_prototypePaths);
+ if (nextScopePath())
+ cont = cont && self.dvReferenceField(visitor, Fields::nextScope, nextScopePath());
+ cont = cont && self.dvWrapField(visitor, Fields::propertyDefs, m_propertyDefs);
+ cont = cont && self.dvWrapField(visitor, Fields::bindings, m_bindings);
+ cont = cont && self.dvWrapField(visitor, Fields::methods, m_methods);
+ cont = cont && self.dvWrapField(visitor, Fields::children, m_children);
+ cont = cont && self.dvWrapField(visitor, Fields::annotations, m_annotations);
+ cont = cont && self.dvItemField(visitor, Fields::propertyInfos, [this, &self]() {
+ return self.subMapItem(Map(
+ pathFromOwner().field(Fields::propertyInfos),
+ [&self](const DomItem &map, const QString &k) {
+ auto pInfo = self.propertyInfoWithName(k);
+ return map.wrap(PathEls::Key(k), pInfo);
+ },
+ [&self](const DomItem &) { return self.propertyInfoNames(); },
+ QLatin1String("PropertyInfo")));
+ });
+ if (m_nameIdentifiers) {
+ cont = cont && self.dvItemField(visitor, Fields::nameIdentifiers, [this, &self]() {
+ return self.subScriptElementWrapperItem(m_nameIdentifiers);
+ });
+ }
+ return cont;
+}
+
+QList<QString> QmlObject::fields() const
+{
+ static QList<QString> myFields(
+ { QString::fromUtf16(Fields::comments), QString::fromUtf16(Fields::idStr),
+ QString::fromUtf16(Fields::name), QString::fromUtf16(Fields::prototypes),
+ QString::fromUtf16(Fields::nextScope), QString::fromUtf16(Fields::propertyDefs),
+ QString::fromUtf16(Fields::bindings), QString::fromUtf16(Fields::methods),
+ QString::fromUtf16(Fields::children), QString::fromUtf16(Fields::annotations),
+ QString::fromUtf16(Fields::propertyInfos) });
+ return myFields;
+}
+
+bool QmlObject::iterateDirectSubpaths(const DomItem &self, DirectVisitor visitor) const
+{
+ bool cont = iterateBaseDirectSubpaths(self, visitor);
+ cont = cont && self.dvValueLazyField(visitor, Fields::defaultPropertyName, [this, &self]() {
+ return defaultPropertyName(self);
+ });
+ return cont;
+}
+
+DomItem QmlObject::field(const DomItem &self, QStringView name) const
+{
+ if (name == Fields::name)
+ return self.subDataItem(PathEls::Field(Fields::name), this->name());
+ if (name == Fields::idStr) {
+ if (idStr().isEmpty())
+ return DomItem();
+ return self.subDataItem(PathEls::Field(Fields::idStr), idStr());
+ }
+ if (name == Fields::methods)
+ return self.wrapField(Fields::methods, m_methods);
+ if (name == Fields::bindings)
+ return self.wrapField(Fields::bindings, m_bindings);
+ if (name == Fields::comments)
+ return CommentableDomElement::field(self, name);
+ if (name == Fields::children)
+ return self.wrapField(Fields::children, m_children);
+
+ if (name == Fields::nextScope) {
+ if (nextScopePath())
+ return self.subReferenceItem(PathEls::Field(Fields::nextScope), nextScopePath());
+ else
+ return DomItem();
+ }
+ if (name == Fields::prototypes) {
+ if (prototypePaths().isEmpty())
+ return DomItem();
+ return self.subReferencesItem(PathEls::Field(Fields::prototypes), m_prototypePaths);
+ }
+ if (name == Fields::annotations)
+ return self.wrapField(Fields::annotations, m_annotations);
+ if (name == Fields::propertyDefs)
+ return self.wrapField(Fields::propertyDefs, m_propertyDefs);
+ if (name == Fields::propertyInfos) {
+ // Need to explicitly copy self here since we might store this and call it later.
+ return self.subMapItem(Map(
+ pathFromOwner().field(Fields::propertyInfos),
+ [copiedSelf = self](const DomItem &map, const QString &k) {
+ return map.wrap(PathEls::Key(k), copiedSelf.propertyInfoWithName(k));
+ },
+ [copiedSelf = self](const DomItem &) { return copiedSelf.propertyInfoNames(); },
+ QLatin1String("PropertyInfo")));
+ }
+ if (name == Fields::nameIdentifiers && m_nameIdentifiers) {
+ return self.subScriptElementWrapperItem(m_nameIdentifiers);
+ }
+ if (name == Fields::defaultPropertyName) {
+ return self.subDataItem(PathEls::Field(Fields::defaultPropertyName),
+ defaultPropertyName(self));
+ }
+ static QStringList knownLookups({ QString::fromUtf16(Fields::fileLocationsTree) });
+ if (!knownLookups.contains(name)) {
+ qCWarning(domLog()) << "Asked non existing field " << name << " in QmlObject "
+ << pathFromOwner();
+ }
+ return DomItem();
+}
+
+void QmlObject::updatePathFromOwner(const Path &newPath)
+{
+ DomElement::updatePathFromOwner(newPath);
+ updatePathFromOwnerMultiMap(m_propertyDefs, newPath.field(Fields::propertyDefs));
+ updatePathFromOwnerMultiMap(m_bindings, newPath.field(Fields::bindings));
+ updatePathFromOwnerMultiMap(m_methods, newPath.field(Fields::methods));
+ updatePathFromOwnerQList(m_children, newPath.field(Fields::children));
+ updatePathFromOwnerQList(m_annotations, newPath.field(Fields::annotations));
+}
+
+QString QmlObject::localDefaultPropertyName() const
+{
+ if (!m_defaultPropertyName.isEmpty())
+ return m_defaultPropertyName;
+ for (const PropertyDefinition &pDef : m_propertyDefs)
+ if (pDef.isDefaultMember)
+ return pDef.name;
+ return QString();
+}
+
+QString QmlObject::defaultPropertyName(const DomItem &self) const
+{
+ QString dProp = localDefaultPropertyName();
+ if (!dProp.isEmpty())
+ return dProp;
+ QString res = QStringLiteral(u"data");
+ self.visitPrototypeChain(
+ [&res](const DomItem &obj) {
+ if (const QmlObject *objPtr = obj.as<QmlObject>()) {
+ QString dProp = objPtr->localDefaultPropertyName();
+ if (!dProp.isEmpty()) {
+ res = dProp;
+ return false;
+ }
+ }
+ return true;
+ },
+ VisitPrototypesOption::SkipFirst);
+ return res;
+}
+
+bool QmlObject::iterateSubOwners(const DomItem &self, function_ref<bool(const DomItem &)> visitor) const
+{
+ bool cont = self.field(Fields::bindings).visitKeys([visitor](const QString &, const DomItem &bs) {
+ return bs.visitIndexes([visitor](const DomItem &b) {
+ DomItem v = b.field(Fields::value);
+ if (std::shared_ptr<ScriptExpression> vPtr = v.ownerAs<ScriptExpression>()) {
+ if (!visitor(v))
+ return false;
+ return v.iterateSubOwners(visitor); // currently not needed, avoid?
+ }
+ return true;
+ });
+ });
+ cont = cont && self.field(Fields::children).visitIndexes([visitor](const DomItem &qmlObj) {
+ if (const QmlObject *qmlObjPtr = qmlObj.as<QmlObject>()) {
+ return qmlObjPtr->iterateSubOwners(qmlObj, visitor);
+ }
+ Q_ASSERT(false);
+ return true;
+ });
+ return cont;
+}
+
+static QStringList dotExpressionToList(const std::shared_ptr<ScriptExpression> &expr)
+{
+ QStringList res;
+ AST::Node *node = (expr ? expr->ast() : nullptr);
+ while (node) {
+ switch (node->kind) {
+ case AST::Node::Kind_IdentifierExpression: {
+ AST::IdentifierExpression *id = AST::cast<AST::IdentifierExpression *>(node);
+ res.prepend(id->name.toString());
+ return res;
+ }
+ case AST::Node::Kind_FieldMemberExpression: {
+ AST::FieldMemberExpression *id = AST::cast<AST::FieldMemberExpression *>(node);
+ res.prepend(id->name.toString());
+ node = id->base;
+ break;
+ }
+ default:
+ qCDebug(writeOutLog).noquote() << "Could not convert dot expression to list for:\n"
+ << expr->astRelocatableDump();
+ return QStringList();
+ }
+ }
+ return res;
+}
+
+LocallyResolvedAlias QmlObject::resolveAlias(const DomItem &self,
+ std::shared_ptr<ScriptExpression> accessSequence) const
+{
+ QStringList accessSequenceList = dotExpressionToList(accessSequence);
+ return resolveAlias(self, accessSequenceList);
+}
+
+LocallyResolvedAlias QmlObject::resolveAlias(const DomItem &self, const QStringList &accessSequence) const
+{
+ LocallyResolvedAlias res;
+ QSet<QString> visitedAlias;
+ if (accessSequence.isEmpty()) {
+ return res;
+ } else if (accessSequence.size() > 3) {
+ res.status = LocallyResolvedAlias::Status::TooDeep;
+ return res;
+ }
+ QString idName = accessSequence.first();
+ DomItem idTarget = self.component()
+ .field(Fields::ids)
+ .key(idName)
+ .index(0)
+ .field(Fields::referredObject)
+ .get();
+ if (!idTarget)
+ return res;
+ res.baseObject = idTarget;
+ res.accessedPath = accessSequence.mid(1);
+ res.typeName = idTarget.name();
+ res.status = LocallyResolvedAlias::Status::ResolvedObject;
+ // check if it refers to locally defined props/objs
+ while (!res.accessedPath.isEmpty()) {
+ QString pNow = res.accessedPath.first();
+ DomItem defNow = res.baseObject.propertyDefs().key(pNow).index(0);
+ if (const PropertyDefinition *defNowPtr = defNow.as<PropertyDefinition>()) {
+ if (defNowPtr->isAlias()) {
+ res.typeName = QString();
+ ++res.nAliases;
+ QString aliasPath = defNow.canonicalPath().toString();
+ if (visitedAlias.contains(aliasPath)) {
+ res.status = LocallyResolvedAlias::Status::Loop;
+ return res;
+ }
+ visitedAlias.insert(aliasPath);
+ DomItem valNow = res.baseObject.bindings().key(pNow).index(0);
+ if (std::shared_ptr<ScriptExpression> exp =
+ valNow.field(Fields::value).ownerAs<ScriptExpression>()) {
+ QStringList expList = dotExpressionToList(exp);
+ if (expList.isEmpty()) {
+ res.status = LocallyResolvedAlias::Status::Invalid;
+ return res;
+ } else if (expList.size() > 3) {
+ res.status = LocallyResolvedAlias::Status::TooDeep;
+ return res;
+ }
+ idName = expList.first();
+ idTarget = self.component()
+ .field(Fields::ids)
+ .key(idName)
+ .index(0)
+ .field(Fields::referredObject)
+ .get();
+ res.baseObject = idTarget;
+ res.accessedPath = expList.mid(1) + res.accessedPath.mid(1);
+ if (!idTarget) {
+ res.status = LocallyResolvedAlias::Status::Invalid;
+ return res;
+ }
+ res.status = LocallyResolvedAlias::Status::ResolvedObject;
+ res.typeName = idTarget.name();
+ } else {
+ res.status = LocallyResolvedAlias::Status::Invalid;
+ return res;
+ }
+ } else {
+ res.localPropertyDef = defNow;
+ res.typeName = defNowPtr->typeName;
+ res.accessedPath = res.accessedPath.mid(1);
+ DomItem valNow = res.baseObject.bindings().key(pNow).index(0).field(Fields::value);
+ if (valNow.internalKind() == DomType::QmlObject) {
+ res.baseObject = valNow;
+ res.typeName = valNow.name();
+ res.status = LocallyResolvedAlias::Status::ResolvedObject;
+ } else {
+ res.status = LocallyResolvedAlias::Status::ResolvedProperty;
+ return res;
+ }
+ }
+ } else {
+ return res;
+ }
+ }
+ return res;
+}
+
+MutableDomItem QmlObject::addPropertyDef(
+ MutableDomItem &self, const PropertyDefinition &propertyDef, AddOption option)
+{
+ Path p = addPropertyDef(propertyDef, option);
+ if (p.last().headIndex(0) > 1)
+ self.owningItemPtr()->addErrorLocal(domParsingErrors().error(
+ tr("Repeated PropertyDefinition with name %1").arg(propertyDef.name)));
+ return self.owner().path(p);
+}
+
+MutableDomItem QmlObject::addBinding(MutableDomItem &self, Binding binding, AddOption option)
+{
+ Path p = addBinding(binding, option);
+ if (p && p.last().headIndex(0) > 1)
+ self.owningItemPtr()->addErrorLocal(
+ domParsingErrors().error(tr("Repeated binding with name %1").arg(binding.name())));
+ return self.owner().path(p);
+}
+
+MutableDomItem QmlObject::addMethod(
+ MutableDomItem &self, const MethodInfo &functionDef, AddOption option)
+{
+ Path p = addMethod(functionDef, option);
+ if (p.last().headIndex(0) > 1)
+ self.owningItemPtr()->addErrorLocal(
+ domParsingErrors().error(tr("Repeated Method with name %1").arg(functionDef.name)));
+ return self.owner().path(p);
+}
+
+void QmlObject::writeOut(const DomItem &self, OutWriter &ow, const QString &onTarget) const
+{
+ const quint32 posOfNewElements = std::numeric_limits<quint32>::max();
+ bool isRootObject = pathFromOwner().length() == 5
+ && pathFromOwner()[0] == Path::Field(Fields::components)
+ && pathFromOwner()[3] == Path::Field(Fields::objects);
+ QString code;
+ DomItem owner = self.owner();
+ if (std::shared_ptr<QmlFile> qmlFilePtr = self.ownerAs<QmlFile>())
+ code = qmlFilePtr->code();
+ ow.writeRegion(IdentifierRegion, name());
+ if (!onTarget.isEmpty())
+ ow.space().writeRegion(OnTokenRegion).space().writeRegion(OnTargetRegion, onTarget);
+ ow.writeRegion(LeftBraceRegion, u" {");
+ int baseIndent = ow.increaseIndent();
+ int spacerId = 0;
+ if (!idStr().isEmpty()) { // *always* put id first
+ DomItem myId = self.component().field(Fields::ids).key(idStr()).index(0);
+ if (myId)
+ myId.writeOutPre(ow);
+ ow.ensureNewline()
+ .writeRegion(IdTokenRegion)
+ .writeRegion(IdColonTokenRegion)
+ .space()
+ .writeRegion(IdNameRegion, idStr());
+ if (ow.lineWriter.options().attributesSequence
+ == LineWriterOptions::AttributesSequence::Normalize) {
+ ow.ensureNewline(2);
+ }
+ if (myId) {
+ myId.writeOutPost(ow);
+ ow.ensureNewline(1);
+ }
+ }
+ quint32 counter = ow.counter();
+ DomItem component;
+ if (isRootObject)
+ component = self.containingObject();
+ auto startLoc = [&](const FileLocations::Tree &l) {
+ if (l)
+ return l->info().fullRegion;
+ return SourceLocation(posOfNewElements, 0, 0, 0);
+ };
+ if (ow.lineWriter.options().attributesSequence
+ == LineWriterOptions::AttributesSequence::Preserve) {
+ QList<QPair<SourceLocation, DomItem>> attribs;
+ AttachedInfoLookupResult<FileLocations::Tree> objLoc =
+ FileLocations::findAttachedInfo(self);
+ FileLocations::Tree componentLoc;
+ if (isRootObject && objLoc.foundTree)
+ componentLoc = objLoc.foundTree->parent()->parent();
+ auto addMMap
+ = [&attribs, &startLoc](const DomItem &base, const FileLocations::Tree &baseLoc) {
+ if (!base)
+ return;
+ const auto values = base.values();
+ for (const auto &els : values) {
+ FileLocations::Tree elsLoc =
+ FileLocations::find(baseLoc, els.pathFromOwner().last());
+ const auto elsValues = els.values();
+ for (const auto &el : elsValues) {
+ FileLocations::Tree elLoc =
+ FileLocations::find(elsLoc, el.pathFromOwner().last());
+ attribs.append(std::make_pair(startLoc(elLoc), el));
+ }
+ }
+ };
+ auto addMyMMap = [this, &objLoc, &self, &addMMap](QStringView fieldName) {
+ DomItem base = this->field(self, fieldName);
+ addMMap(base, FileLocations::find(objLoc.foundTree, base.pathFromOwner().last()));
+ };
+ auto addSingleLevel
+ = [&attribs, &startLoc](const DomItem &base, const FileLocations::Tree &baseLoc) {
+ if (!base)
+ return;
+ const auto baseValues = base.values();
+ for (const auto &el : baseValues) {
+ FileLocations::Tree elLoc = FileLocations::find(baseLoc, el.pathFromOwner().last());
+ attribs.append(std::make_pair(startLoc(elLoc), el));
+ }
+ };
+ if (isRootObject) {
+ DomItem enums = component.field(Fields::enumerations);
+ addMMap(enums, FileLocations::find(componentLoc, enums.pathFromOwner().last()));
+ }
+ addMyMMap(Fields::propertyDefs);
+ addMyMMap(Fields::bindings);
+ addMyMMap(Fields::methods);
+ DomItem children = field(self, Fields::children);
+ addSingleLevel(children,
+ FileLocations::find(objLoc.foundTree, children.pathFromOwner().last()));
+ if (isRootObject) {
+ DomItem subCs = component.field(Fields::subComponents);
+ for (const DomItem &c : subCs.values()) {
+ AttachedInfoLookupResult<FileLocations::Tree> subLoc =
+ FileLocations::findAttachedInfo(c);
+ Q_ASSERT(subLoc.foundTree);
+ attribs.append(std::make_pair(startLoc(subLoc.foundTree), c));
+ }
+ }
+ std::stable_sort(attribs.begin(), attribs.end(),
+ [](const std::pair<SourceLocation, DomItem> &el1,
+ const std::pair<SourceLocation, DomItem> &el2) {
+ if (el1.first.offset < el2.first.offset)
+ return true;
+ if (el1.first.offset > el2.first.offset)
+ return false;
+ int i = int(el1.second.internalKind())
+ - int(el2.second.internalKind());
+ return i < 0;
+ });
+ qsizetype iAttr = 0;
+ while (iAttr != attribs.size()) {
+ auto &el = attribs[iAttr++];
+ // check for an empty line before the current element, and preserve it
+ int preNewlines = 0;
+ quint32 start = el.first.offset;
+ if (start != posOfNewElements && size_t(code.size()) >= start) {
+ while (start != 0) {
+ QChar c = code.at(--start);
+ if (c == u'\n') {
+ if (++preNewlines == 2)
+ break;
+ } else if (!c.isSpace())
+ break;
+ }
+ }
+ if (preNewlines == 0)
+ ++preNewlines;
+ ow.ensureNewline(preNewlines);
+ if (el.second.internalKind() == DomType::PropertyDefinition && iAttr != attribs.size()
+ && el.first.offset != ~quint32(0)) {
+ DomItem b;
+ auto &bPair = attribs[iAttr];
+ if (bPair.second.internalKind() == DomType::Binding
+ && bPair.first.begin() < el.first.end()
+ && bPair.second.name() == el.second.name()) {
+ b = bPair.second;
+ ++iAttr;
+ b.writeOutPre(ow);
+ }
+ el.second.writeOut(ow);
+ if (b) {
+ ow.write(u": ");
+ if (const Binding *bPtr = b.as<Binding>())
+ bPtr->writeOutValue(b, ow);
+ else {
+ qWarning() << "Internal error casting binding to Binding in"
+ << b.canonicalPath();
+ ow.writeRegion(LeftBraceRegion).writeRegion(RightBraceRegion);
+ }
+ b.writeOutPost(ow);
+ }
+ } else {
+ el.second.writeOut(ow);
+ }
+ ow.ensureNewline();
+ }
+ ow.decreaseIndent(1, baseIndent);
+ ow.writeRegion(RightBraceRegion);
+
+ return;
+ }
+ DomItem bindings = field(self, Fields::bindings);
+ DomItem propertyDefs = field(self, Fields::propertyDefs);
+
+ if (isRootObject) {
+ const auto descs = component.field(Fields::enumerations).values();
+ for (const auto &enumDescs : descs) {
+ const auto values = enumDescs.values();
+ for (const auto &enumDesc : values) {
+ ow.ensureNewline(1);
+ enumDesc.writeOut(ow);
+ ow.ensureNewline(1);
+ }
+ }
+ }
+ if (counter != ow.counter() || !idStr().isEmpty())
+ spacerId = ow.addNewlinesAutospacerCallback(2);
+ QSet<QString> mergedDefBinding;
+ for (const QString &defName : propertyDefs.sortedKeys()) {
+ const auto pDefs = propertyDefs.key(defName).values();
+ for (const auto &pDef : pDefs) {
+ const PropertyDefinition *pDefPtr = pDef.as<PropertyDefinition>();
+ Q_ASSERT(pDefPtr);
+ DomItem b;
+ bool uniqueDeclarationWithThisName = pDefs.size() == 1;
+ if (uniqueDeclarationWithThisName && !pDefPtr->isRequired)
+ bindings.key(pDef.name()).visitIndexes([&b, pDefPtr](const DomItem &el) {
+ const Binding *elPtr = el.as<Binding>();
+ if (elPtr && elPtr->bindingType() == BindingType::Normal) {
+ switch (elPtr->valueKind()) {
+ case BindingValueKind::ScriptExpression:
+ b = el;
+ break;
+ case BindingValueKind::Array:
+ if (!pDefPtr->isDefaultMember
+ && pDefPtr->isParametricType())
+ b = el;
+ break;
+ case BindingValueKind::Object:
+ if (!pDefPtr->isDefaultMember
+ && !pDefPtr->isParametricType())
+ b = el;
+ break;
+ case BindingValueKind::Empty:
+ break;
+ }
+ return false;
+ }
+ return true;
+ });
+ if (b) {
+ mergedDefBinding.insert(defName);
+ b.writeOutPre(ow);
+ }
+ pDef.writeOut(ow);
+ if (b) {
+ ow.write(u": ");
+ if (const Binding *bPtr = b.as<Binding>())
+ bPtr->writeOutValue(b, ow);
+ else {
+ qWarning() << "Internal error casting binding to Binding in"
+ << b.canonicalPath();
+ ow.writeRegion(LeftBraceRegion).writeRegion(RightBraceRegion);
+ }
+ b.writeOutPost(ow);
+ }
+ }
+ }
+ ow.removeTextAddCallback(spacerId);
+ QList<DomItem> signalList, methodList;
+ const auto fields = field(self, Fields::methods).values();
+ for (const auto &ms : fields) {
+ const auto values = ms.values();
+ for (const auto &m : values) {
+ const MethodInfo *mPtr = m.as<MethodInfo>();
+ if (mPtr && mPtr->methodType == MethodInfo::MethodType::Signal)
+ signalList.append(m);
+ else
+ methodList.append(m);
+ }
+ }
+ if (counter != ow.counter())
+ spacerId = ow.addNewlinesAutospacerCallback(2);
+ for (const auto &sig : std::as_const(signalList)) {
+ ow.ensureNewline();
+ sig.writeOut(ow);
+ ow.ensureNewline();
+ }
+ ow.removeTextAddCallback(spacerId);
+ if (counter != ow.counter())
+ spacerId = ow.addNewlinesAutospacerCallback(2);
+ bool first = true;
+ for (const auto &method : std::as_const(methodList)) {
+ if (!first && ow.lineWriter.options().functionsSpacing) {
+ ow.newline();
+ }
+ ow.ensureNewline();
+ first = false;
+ method.writeOut(ow);
+ ow.ensureNewline();
+ }
+ ow.removeTextAddCallback(spacerId);
+ QList<DomItem> normalBindings, signalHandlers, delayedBindings;
+ for (const auto &bName : bindings.sortedKeys()) {
+ bool skipFirstNormal = mergedDefBinding.contains(bName);
+ const auto values = bindings.key(bName).values();
+ for (const auto &b : values) {
+ const Binding *bPtr = b.as<Binding>();
+ if (skipFirstNormal) {
+ if (bPtr && bPtr->bindingType() == BindingType::Normal) {
+ skipFirstNormal = false;
+ continue;
+ }
+ }
+ if (bPtr->valueKind() == BindingValueKind::Array
+ || bPtr->valueKind() == BindingValueKind::Object)
+ delayedBindings.append(b);
+ else if (b.field(Fields::isSignalHandler).value().toBool(false))
+ signalHandlers.append(b);
+ else
+ normalBindings.append(b);
+ }
+ }
+ if (counter != ow.counter())
+ spacerId = ow.addNewlinesAutospacerCallback(2);
+ for (const auto &b : std::as_const(normalBindings))
+ b.writeOut(ow);
+ ow.removeTextAddCallback(spacerId);
+ if (counter != ow.counter())
+ spacerId = ow.addNewlinesAutospacerCallback(2);
+ for (const auto &b : std::as_const(delayedBindings))
+ b.writeOut(ow);
+ ow.removeTextAddCallback(spacerId);
+ if (counter != ow.counter())
+ spacerId = ow.addNewlinesAutospacerCallback(2);
+ for (const auto &b : std::as_const(signalHandlers))
+ b.writeOut(ow);
+ ow.removeTextAddCallback(spacerId);
+ if (counter != ow.counter())
+ spacerId = ow.addNewlinesAutospacerCallback(2);
+ first = true;
+ const auto values = field(self, Fields::children).values();
+ for (const auto &c : values) {
+ if (!first && ow.lineWriter.options().objectsSpacing) {
+ ow.newline().newline();
+ }
+ first = false;
+ ow.ensureNewline();
+ c.writeOut(ow);
+ }
+ ow.removeTextAddCallback(spacerId);
+ if (isRootObject) {
+ // we are a root object, possibly add components
+ DomItem subComps = component.field(Fields::subComponents);
+ if (counter != ow.counter())
+ spacerId = ow.addNewlinesAutospacerCallback(2);
+ const auto values = subComps.values();
+ for (const auto &subC : values) {
+ ow.ensureNewline();
+ subC.writeOut(ow);
+ }
+ ow.removeTextAddCallback(spacerId);
+ }
+ ow.decreaseIndent(1, baseIndent);
+ ow.ensureNewline().writeRegion(RightBraceRegion);
+}
+
+Binding::Binding(const QString &name, std::unique_ptr<BindingValue> value, BindingType bindingType)
+ : m_bindingType(bindingType), m_name(name), m_value(std::move(value))
+{
+}
+
+Binding::Binding(
+ const QString &name, const std::shared_ptr<ScriptExpression> &value,
+ BindingType bindingType)
+ : Binding(name, std::make_unique<BindingValue>(value), bindingType)
+{
+}
+
+Binding::Binding(const QString &name, const QString &scriptCode, BindingType bindingType)
+ : Binding(name,
+ std::make_unique<BindingValue>(std::make_shared<ScriptExpression>(
+ scriptCode, ScriptExpression::ExpressionType::BindingExpression, 0,
+ Binding::preCodeForName(name), Binding::postCodeForName(name))),
+ bindingType)
+{
+}
+
+Binding::Binding(const QString &name, const QmlObject &value, BindingType bindingType)
+ : Binding(name, std::make_unique<BindingValue>(value), bindingType)
+{
+}
+
+Binding::Binding(const QString &name, const QList<QmlObject> &value, BindingType bindingType)
+ : Binding(name, std::make_unique<BindingValue>(value), bindingType)
+{
+}
+
+Binding::Binding(const Binding &o)
+ : m_bindingType(o.m_bindingType),
+ m_name(o.m_name),
+ m_annotations(o.m_annotations),
+ m_comments(o.m_comments),
+ m_bindingIdentifiers(o.m_bindingIdentifiers)
+{
+ if (o.m_value) {
+ m_value = std::make_unique<BindingValue>(*o.m_value);
+ }
+}
+
+Binding::~Binding() { }
+
+Binding &Binding::operator=(const Binding &o)
+{
+ m_name = o.m_name;
+ m_bindingType = o.m_bindingType;
+ m_annotations = o.m_annotations;
+ m_comments = o.m_comments;
+ m_bindingIdentifiers = o.m_bindingIdentifiers;
+ if (o.m_value) {
+ if (!m_value)
+ m_value = std::make_unique<BindingValue>(*o.m_value);
+ else
+ *m_value = *o.m_value;
+ } else {
+ m_value.reset();
+ }
+ return *this;
+}
+
+bool Binding::iterateDirectSubpaths(const DomItem &self, DirectVisitor visitor) const
+{
+ bool cont = true;
+ cont = cont && self.dvValueField(visitor, Fields::name, m_name);
+ cont = cont && self.dvValueField(visitor, Fields::isSignalHandler, isSignalHandler());
+ if (!m_value)
+ cont = cont && visitor(PathEls::Field(Fields::value), []() { return DomItem(); });
+ else
+ cont = cont && self.dvItemField(visitor, Fields::value, [this, &self]() {
+ return m_value->value(self);
+ });
+ cont = cont && self.dvValueField(visitor, Fields::bindingType, int(m_bindingType));
+ cont = cont && self.dvWrapField(visitor, Fields::comments, m_comments);
+ cont = cont && self.dvValueLazyField(visitor, Fields::preCode, [this]() {
+ return this->preCode();
+ });
+ cont = cont && self.dvValueLazyField(visitor, Fields::postCode, [this]() {
+ return this->postCode();
+ });
+ if (m_bindingIdentifiers) {
+ cont = cont && self.dvItemField(visitor, Fields::bindingIdentifiers, [this, &self]() {
+ return self.subScriptElementWrapperItem(m_bindingIdentifiers);
+ });
+ }
+ cont = cont && self.dvWrapField(visitor, Fields::annotations, m_annotations);
+ return cont;
+}
+
+DomItem Binding::valueItem(const DomItem &self) const
+{
+ if (!m_value)
+ return DomItem();
+ return m_value->value(self);
+}
+
+BindingValueKind Binding::valueKind() const
+{
+ if (!m_value)
+ return BindingValueKind::Empty;
+ return m_value->kind;
+}
+
+QmlObject const *Binding::objectValue() const
+{
+ if (valueKind() == BindingValueKind::Object)
+ return &(m_value->object);
+ return nullptr;
+}
+
+QmlObject *Binding::objectValue()
+{
+ if (valueKind() == BindingValueKind::Object)
+ return &(m_value->object);
+ return nullptr;
+}
+
+QList<QmlObject> const *Binding::arrayValue() const
+{
+ if (valueKind() == BindingValueKind::Array)
+ return &(m_value->array);
+ return nullptr;
+}
+
+QList<QmlObject> *Binding::arrayValue()
+{
+ if (valueKind() == BindingValueKind::Array)
+ return &(m_value->array);
+ return nullptr;
+}
+
+std::shared_ptr<ScriptExpression> Binding::scriptExpressionValue() const
+{
+ if (valueKind() == BindingValueKind::ScriptExpression)
+ return m_value->scriptExpression;
+ return nullptr;
+}
+
+std::shared_ptr<ScriptExpression> Binding::scriptExpressionValue()
+{
+ if (valueKind() == BindingValueKind::ScriptExpression)
+ return m_value->scriptExpression;
+ return nullptr;
+}
+
+Path Binding::addAnnotation(const Path &selfPathFromOwner, const QmlObject &annotation, QmlObject **aPtr)
+{
+ return appendUpdatableElementInQList(selfPathFromOwner.field(Fields::annotations),
+ m_annotations, annotation, aPtr);
+}
+
+void Binding::updatePathFromOwner(const Path &newPath)
+{
+ Path base = newPath.field(Fields::annotations);
+ if (m_value)
+ m_value->updatePathFromOwner(newPath.field(Fields::value));
+ updatePathFromOwnerQList(m_annotations, newPath.field(Fields::annotations));
+}
+
+void Binding::writeOut(const DomItem &self, OutWriter &lw) const
+{
+ lw.ensureNewline();
+ if (m_bindingType == BindingType::Normal) {
+ lw.writeRegion(IdentifierRegion, name());
+ lw.writeRegion(ColonTokenRegion).space();
+ writeOutValue(self, lw);
+ } else {
+ DomItem v = valueItem(self);
+ if (const QmlObject *vPtr = v.as<QmlObject>()) {
+ v.writeOutPre(lw);
+ vPtr->writeOut(v, lw, name());
+ v.writeOutPost(lw);
+ } else {
+ qCWarning(writeOutLog()) << "On Binding requires an QmlObject Value, not "
+ << v.internalKindStr() << " at " << self.canonicalPath();
+ }
+ }
+}
+
+void Binding::writeOutValue(const DomItem &self, OutWriter &lw) const
+{
+ DomItem v = valueItem(self);
+ switch (valueKind()) {
+ case BindingValueKind::Empty:
+ qCWarning(writeOutLog()) << "Writing of empty binding " << name();
+ lw.write(u"{}");
+ break;
+ case BindingValueKind::Array:
+ if (const List *vPtr = v.as<List>()) {
+ v.writeOutPre(lw);
+ vPtr->writeOut(v, lw, false);
+ v.writeOutPost(lw);
+ }
+ break;
+ case BindingValueKind::Object:
+ case BindingValueKind::ScriptExpression:
+ v.writeOut(lw);
+ break;
+ }
+}
+
+bool QmltypesComponent::iterateDirectSubpaths(const DomItem &self, DirectVisitor visitor) const
+{
+ bool cont = Component::iterateDirectSubpaths(self, visitor);
+ cont = cont && self.dvWrapField(visitor, Fields::exports, m_exports);
+ cont = cont && self.dvValueField(visitor, Fields::metaRevisions, m_metaRevisions);
+ if (!fileName().isEmpty())
+ cont = cont && self.dvValueField(visitor, Fields::fileName, fileName()); // remove?
+ cont = cont && self.dvValueField(visitor, Fields::interfaceNames, m_interfaceNames);
+ cont = cont && self.dvValueField(visitor, Fields::hasCustomParser, m_hasCustomParser);
+ cont = cont && self.dvValueField(visitor, Fields::valueTypeName, m_valueTypeName);
+ cont = cont && self.dvValueField(visitor, Fields::extensionTypeName, m_extensionTypeName);
+ cont = cont && self.dvValueField(visitor, Fields::accessSemantics, int(m_accessSemantics));
+ return cont;
+}
+
+Export Export::fromString(
+ const Path &source, QStringView exp, const Path &typePath, const ErrorHandler &h)
+{
+ Export res;
+ res.exportSourcePath = source;
+ res.typePath = typePath;
+ int slashIdx = exp.indexOf(QLatin1Char('/'));
+ int spaceIdx = exp.indexOf(QLatin1Char(' '));
+ if (spaceIdx == -1)
+ spaceIdx = exp.size();
+ else
+ res.version = Version::fromString(exp.mid(spaceIdx + 1));
+ if (!res.version.isValid())
+ domParsingErrors()
+ .error(tr("Expected string literal to contain 'Package/Name major.minor' "
+ "or 'Name major.minor' not '%1'.")
+ .arg(exp))
+ .handle(h);
+ if (slashIdx != -1)
+ res.uri = exp.left(slashIdx).toString();
+ res.typeName = exp.mid(slashIdx + 1, spaceIdx - (slashIdx + 1)).toString();
+ return res;
+}
+
+bool AttributeInfo::iterateDirectSubpaths(const DomItem &self, DirectVisitor visitor) const
+{
+ bool cont = true;
+ cont = cont && self.dvValueField(visitor, Fields::name, name);
+ cont = cont && self.dvValueField(visitor, Fields::access, int(access));
+ cont = cont && self.dvValueField(visitor, Fields::typeName, typeName);
+ cont = cont && self.dvValueField(visitor, Fields::isReadonly, isReadonly);
+ cont = cont && self.dvValueField(visitor, Fields::isList, isList);
+ cont = cont && self.dvWrapField(visitor, Fields::comments, comments);
+ cont = cont && self.dvWrapField(visitor, Fields::annotations, annotations);
+ return cont;
+}
+
+Path AttributeInfo::addAnnotation(const Path &selfPathFromOwner, const QmlObject &annotation,
+ QmlObject **aPtr)
+{
+ return appendUpdatableElementInQList(selfPathFromOwner.field(Fields::annotations), annotations,
+ annotation, aPtr);
+}
+
+void AttributeInfo::updatePathFromOwner(const Path &newPath)
+{
+ Path base = newPath.field(Fields::annotations);
+ updatePathFromOwnerQList(annotations, newPath.field(Fields::annotations));
+}
+
+bool EnumDecl::iterateDirectSubpaths(const DomItem &self, DirectVisitor visitor) const
+{
+ bool cont = CommentableDomElement::iterateDirectSubpaths(self, visitor);
+ cont = cont && self.dvValueField(visitor, Fields::name, name());
+ cont = cont && self.dvWrapField(visitor, Fields::values, m_values);
+ cont = cont && self.dvWrapField(visitor, Fields::annotations, m_annotations);
+ return cont;
+}
+
+void EnumDecl::updatePathFromOwner(const Path &newPath)
+{
+ DomElement::updatePathFromOwner(newPath);
+ updatePathFromOwnerQList(m_annotations, newPath.field(Fields::annotations));
+}
+
+void EnumDecl::setAnnotations(const QList<QmlObject> &annotations)
+{
+ m_annotations = annotations;
+}
+
+Path EnumDecl::addAnnotation(const QmlObject &annotation, QmlObject **aPtr)
+{
+ return appendUpdatableElementInQList(pathFromOwner().field(Fields::annotations), m_annotations,
+ annotation, aPtr);
+}
+
+void EnumDecl::writeOut(const DomItem &self, OutWriter &ow) const
+{
+ ow.writeRegion(EnumKeywordRegion)
+ .space()
+ .writeRegion(IdentifierRegion, name())
+ .space()
+ .writeRegion(LeftBraceRegion);
+ int iLevel = ow.increaseIndent(1);
+ const auto values = self.field(Fields::values).values();
+ for (const auto &value : values) {
+ ow.ensureNewline();
+ value.writeOut(ow);
+ }
+ ow.decreaseIndent(1, iLevel);
+ ow.ensureNewline().writeRegion(RightBraceRegion);
+}
+
+QList<Path> ImportScope::allSources(const DomItem &self) const
+{
+ DomItem top = self.top();
+ DomItem env = top.environment();
+ Path selfPath = self.canonicalPath().field(Fields::allSources);
+ RefCacheEntry cached = (env ? RefCacheEntry::forPath(env, selfPath) : RefCacheEntry());
+ if (cached.cached == RefCacheEntry::Cached::All)
+ return cached.canonicalPaths;
+ QList<Path> res;
+ QSet<Path> knownPaths;
+ QList<Path> toDo(m_importSourcePaths.rbegin(), m_importSourcePaths.rend());
+ while (!toDo.isEmpty()) {
+ Path pNow = toDo.takeLast();
+ if (knownPaths.contains(pNow))
+ continue;
+ knownPaths.insert(pNow);
+ res.append(pNow);
+ DomItem sourceBase = top.path(pNow);
+ for (const DomItem &autoExp : sourceBase.field(Fields::autoExports).values()) {
+ if (const ModuleAutoExport *autoExpPtr = autoExp.as<ModuleAutoExport>()) {
+ Path newSource;
+ if (autoExpPtr->inheritVersion) {
+ Version v = autoExpPtr->import.version;
+ DomItem sourceVersion = sourceBase.field(Fields::version);
+ if (const Version *sourceVersionPtr = sourceVersion.as<Version>()) {
+ if (v.majorVersion < 0)
+ v.majorVersion = sourceVersionPtr->majorVersion;
+ if (v.minorVersion < 0)
+ v.minorVersion = sourceVersionPtr->minorVersion;
+ } else {
+ qWarning() << "autoExport with inherited version " << autoExp
+ << " but missing version in source" << pNow;
+ }
+ Import toImport(autoExpPtr->import.uri, v);
+ newSource = toImport.importedPath();
+ } else {
+ newSource = autoExpPtr->import.importedPath();
+ }
+ if (newSource && !knownPaths.contains(newSource))
+ toDo.append(newSource);
+ } else {
+ qWarning() << "expected ModuleAutoExport not " << autoExp.internalKindStr()
+ << "looking up autoExports of" << sourceBase;
+ Q_ASSERT(false);
+ }
+ }
+ }
+ if (env)
+ RefCacheEntry::addForPath(env, selfPath, RefCacheEntry { RefCacheEntry::Cached::All, res });
+ return res;
+}
+
+bool ImportScope::iterateDirectSubpaths(const DomItem &self, DirectVisitor visitor) const
+{
+ bool cont = true;
+ cont = cont && self.dvReferencesField(visitor, Fields::importSources, m_importSourcePaths);
+ cont = cont && self.dvItemField(visitor, Fields::allSources, [this, &self]() -> DomItem {
+ return self.subListItem(List::fromQList<Path>(
+ self.pathFromOwner().field(Fields::allSources), allSources(self),
+ [](const DomItem &list, const PathEls::PathComponent &p, const Path &el) {
+ return list.subDataItem(p, el.toString());
+ }));
+ });
+ cont = cont && self.dvWrapField(visitor, Fields::qualifiedImports, m_subImports);
+ cont = cont && self.dvItemField(visitor, Fields::imported, [this, &self]() -> DomItem {
+ return self.subMapItem(Map(
+ self.pathFromOwner().field(Fields::imported),
+ [this, &self](const DomItem &map, const QString &key) {
+ return map.subListItem(List::fromQList<DomItem>(
+ map.pathFromOwner().key(key), importedItemsWithName(self, key),
+ [](const DomItem &, const PathEls::PathComponent &, const DomItem &el) {
+ return el;
+ }));
+ },
+ [this, &self](const DomItem &) { return this->importedNames(self); },
+ QLatin1String("List<Export>")));
+ });
+ return cont;
+}
+
+bool PropertyInfo::iterateDirectSubpaths(const DomItem &self, DirectVisitor visitor) const
+{
+ bool cont = true;
+ cont = cont && self.dvValueField(visitor, Fields::propertyDefs, propertyDefs);
+ cont = cont && self.dvValueField(visitor, Fields::bindings, bindings);
+ return cont;
+}
+
+BindingValue::BindingValue() : kind(BindingValueKind::Empty) { }
+
+BindingValue::BindingValue(const QmlObject &o) : kind(BindingValueKind::Object)
+{
+ new (&object) QmlObject(o);
+}
+
+BindingValue::BindingValue(const std::shared_ptr<ScriptExpression> &o)
+ : kind(BindingValueKind::ScriptExpression)
+{
+ new (&scriptExpression) std::shared_ptr<ScriptExpression>(o);
+}
+
+BindingValue::BindingValue(const QList<QmlObject> &l) : kind(BindingValueKind::Array)
+{
+ new (&array) QList<QmlObject>(l);
+}
+
+BindingValue::~BindingValue()
+{
+ clearValue();
+}
+
+BindingValue::BindingValue(const BindingValue &o) : kind(o.kind)
+{
+ switch (o.kind) {
+ case BindingValueKind::Empty:
+ break;
+ case BindingValueKind::Object:
+ new (&object) QmlObject(o.object);
+ break;
+ case BindingValueKind::ScriptExpression:
+ new (&scriptExpression) std::shared_ptr<ScriptExpression>(o.scriptExpression);
+ break;
+ case BindingValueKind::Array:
+ new (&array) QList<QmlObject>(o.array);
+ }
+}
+
+BindingValue &BindingValue::operator=(const BindingValue &o)
+{
+ clearValue();
+ kind = o.kind;
+ switch (o.kind) {
+ case BindingValueKind::Empty:
+ break;
+ case BindingValueKind::Object:
+ new (&object) QmlObject(o.object);
+ break;
+ case BindingValueKind::ScriptExpression:
+ new (&scriptExpression) std::shared_ptr<ScriptExpression>(o.scriptExpression);
+ break;
+ case BindingValueKind::Array:
+ new (&array) QList<QmlObject>(o.array);
+ }
+ return *this;
+}
+
+DomItem BindingValue::value(const DomItem &binding) const
+{
+ switch (kind) {
+ case BindingValueKind::Empty:
+ break;
+ case BindingValueKind::Object:
+ return binding.copy(&object);
+ case BindingValueKind::ScriptExpression:
+ return binding.subOwnerItem(PathEls::Field(Fields::value), scriptExpression);
+ case BindingValueKind::Array:
+ return binding.subListItem(List::fromQListRef<QmlObject>(
+ binding.pathFromOwner().field(u"value"), array,
+ [](const DomItem &self, const PathEls::PathComponent &, const QmlObject &obj) {
+ return self.copy(&obj);
+ }));
+ }
+ return DomItem();
+}
+
+void BindingValue::updatePathFromOwner(const Path &newPath)
+{
+ switch (kind) {
+ case BindingValueKind::Empty:
+ break;
+ case BindingValueKind::Object:
+ object.updatePathFromOwner(newPath);
+ break;
+ case BindingValueKind::ScriptExpression:
+ break;
+ case BindingValueKind::Array:
+ updatePathFromOwnerQList(array, newPath);
+ break;
+ }
+}
+
+void BindingValue::clearValue()
+{
+ switch (kind) {
+ case BindingValueKind::Empty:
+ break;
+ case BindingValueKind::Object:
+ object.~QmlObject();
+ break;
+ case BindingValueKind::ScriptExpression:
+ scriptExpression.~shared_ptr();
+ break;
+ case BindingValueKind::Array:
+ array.~QList<QmlObject>();
+ break;
+ }
+ kind = BindingValueKind::Empty;
+}
+
+ScriptExpression::ScriptExpression(
+ QStringView code, const std::shared_ptr<QQmlJS::Engine> &engine, AST::Node *ast,
+ const std::shared_ptr<AstComments> &comments, ExpressionType expressionType,
+ SourceLocation localOffset, int derivedFrom, QStringView preCode, QStringView postCode)
+ : OwningItem(derivedFrom),
+ m_expressionType(expressionType),
+ m_code(code),
+ m_preCode(preCode),
+ m_postCode(postCode),
+ m_engine(engine),
+ m_ast(ast),
+ m_astComments(comments),
+ m_localOffset(localOffset)
+{
+ if (m_expressionType == ExpressionType::BindingExpression)
+ if (AST::ExpressionStatement *exp = AST::cast<AST::ExpressionStatement *>(m_ast))
+ m_ast = exp->expression;
+ Q_ASSERT(m_astComments);
+}
+
+ScriptExpression::ScriptExpression(const ScriptExpression &e) : OwningItem(e)
+{
+ QMutexLocker l(mutex());
+ m_expressionType = e.m_expressionType;
+ m_engine = e.m_engine;
+ m_ast = e.m_ast;
+ if (m_codeStr.isEmpty()) {
+ m_code = e.m_code;
+ } else {
+ m_codeStr = e.m_codeStr;
+ m_code = m_codeStr;
+ }
+ m_localOffset = e.m_localOffset;
+ m_astComments = e.m_astComments;
+}
+
+std::shared_ptr<ScriptExpression> ScriptExpression::copyWithUpdatedCode(
+ const DomItem &self, const QString &code) const
+{
+ std::shared_ptr<ScriptExpression> copy = makeCopy(self);
+ DomItem container = self.containingObject();
+ QString preCodeStr = container.field(Fields::preCode).value().toString(m_preCode.toString());
+ QString postCodeStr = container.field(Fields::postCode).value().toString(m_postCode.toString());
+ copy->setCode(code, preCodeStr, postCodeStr);
+ return copy;
+}
+
+bool ScriptExpression::iterateDirectSubpaths(const DomItem &self, DirectVisitor visitor) const
+{
+ bool cont = OwningItem::iterateDirectSubpaths(self, visitor);
+ cont = cont && self.dvValueField(visitor, Fields::code, code());
+ if (!preCode().isEmpty())
+ cont = cont
+ && self.dvValueField(visitor, Fields::preCode, preCode(),
+ ConstantData::Options::MapIsMap);
+ if (!postCode().isEmpty())
+ cont = cont
+ && self.dvValueField(visitor, Fields::postCode, postCode(),
+ ConstantData::Options::MapIsMap);
+ cont = cont
+ && self.dvValueLazyField(
+ visitor, Fields::localOffset,
+ [this]() { return sourceLocationToQCborValue(localOffset()); },
+ ConstantData::Options::MapIsMap);
+ cont = cont && self.dvValueLazyField(visitor, Fields::astRelocatableDump, [this]() {
+ return astRelocatableDump();
+ });
+ cont = cont && self.dvValueField(visitor, Fields::expressionType, int(expressionType()));
+ if (m_element) {
+ cont = cont && self.dvItemField(visitor, Fields::scriptElement, [this, &self]() {
+ return self.subScriptElementWrapperItem(m_element);
+ });
+ }
+ return cont;
+}
+
+class FirstNodeVisitor : public VisitAll
+{
+public:
+ quint32 minStart = 0;
+ quint32 maxEnd = std::numeric_limits<quint32>::max();
+ AST::Node *firstNodeInRange = nullptr;
+
+ FirstNodeVisitor(quint32 minStart = 0, quint32 maxEnd = std::numeric_limits<quint32>::max())
+ : minStart(minStart), maxEnd(maxEnd)
+ {
+ }
+
+ bool preVisit(AST::Node *n) override
+ {
+ if (!VisitAll::uiKinds().contains(n->kind)) {
+ quint32 start = n->firstSourceLocation().begin();
+ quint32 end = n->lastSourceLocation().end();
+ if (!firstNodeInRange && minStart <= start && end <= maxEnd && start < end)
+ firstNodeInRange = n;
+ }
+ return !firstNodeInRange;
+ }
+};
+
+AST::Node *firstNodeInRange(AST::Node *n, quint32 minStart = 0, quint32 maxEnd = ~quint32(0))
+{
+ FirstNodeVisitor visitor(minStart, maxEnd);
+ AST::Node::accept(n, &visitor);
+ return visitor.firstNodeInRange;
+}
+
+void ScriptExpression::setCode(const QString &code, const QString &preCode, const QString &postCode)
+{
+ // TODO QTBUG-121933
+ m_codeStr = code;
+ QString resolvedPreCode, resolvedPostCode;
+ if (m_expressionType == ExpressionType::BindingExpression && preCode.isEmpty()) {
+ resolvedPreCode = Binding::preCodeForName(u"binding");
+ resolvedPostCode = Binding::postCodeForName(u"binding");
+ } else {
+ resolvedPreCode = preCode;
+ resolvedPostCode = postCode;
+ }
+ if (!resolvedPreCode.isEmpty() || !resolvedPostCode.isEmpty())
+ m_codeStr = resolvedPreCode + code + resolvedPostCode;
+ m_code = QStringView(m_codeStr).mid(resolvedPreCode.size(), code.size());
+ m_preCode = QStringView(m_codeStr).mid(0, resolvedPreCode.size());
+ m_postCode = QStringView(m_codeStr).mid(
+ resolvedPreCode.size() + code.size(), resolvedPostCode.size());
+ m_engine = nullptr;
+ m_ast = nullptr;
+ m_localOffset = SourceLocation();
+ if (!m_code.isEmpty()) {
+ IndentInfo preChange(m_preCode, 4);
+ m_localOffset.offset = m_preCode.size();
+ m_localOffset.length = m_code.size();
+ m_localOffset.startColumn = preChange.trailingString.size();
+ m_localOffset.startLine = preChange.nNewlines;
+ m_engine = std::make_shared<QQmlJS::Engine>();
+ m_astComments = std::make_shared<AstComments>(m_engine);
+ m_ast = parse(resolveParseMode());
+
+ if (AST::Program *programPtr = AST::cast<AST::Program *>(m_ast)) {
+ m_ast = programPtr->statements;
+ }
+ if (!m_preCode.isEmpty())
+ m_ast = firstNodeInRange(m_ast, m_preCode.size(),
+ m_preCode.size() + m_code.size());
+ if (auto *sList = AST::cast<AST::FormalParameterList *>(m_ast)) {
+ m_ast = sList->element;
+ }
+ if (m_expressionType != ExpressionType::FunctionBody) {
+ if (AST::StatementList *sList = AST::cast<AST::StatementList *>(m_ast)) {
+ if (!sList->next)
+ m_ast = sList->statement;
+ }
+ }
+ if (m_expressionType == ExpressionType::BindingExpression)
+ if (AST::ExpressionStatement *exp = AST::cast<AST::ExpressionStatement *>(m_ast))
+ m_ast = exp->expression;
+
+ CommentCollector collector;
+ collector.collectComments(m_engine, m_ast, m_astComments);
+ }
+}
+
+AST::Node *ScriptExpression::parse(const ParseMode mode)
+{
+ QQmlJS::Lexer lexer(m_engine.get());
+ lexer.setCode(m_codeStr, /*lineno = */ 1, /*qmlMode=*/mode == ParseMode::QML);
+ QQmlJS::Parser parser(m_engine.get());
+ const bool parserSucceeded = [mode, &parser]() {
+ switch (mode) {
+ case ParseMode::QML:
+ return parser.parse();
+ case ParseMode::JS:
+ return parser.parseScript();
+ case ParseMode::ESM:
+ return parser.parseModule();
+ default:
+ Q_UNREACHABLE_RETURN(false);
+ }
+ }();
+ if (!parserSucceeded) {
+ addErrorLocal(domParsingErrors().error(tr("Parsing of code failed")));
+ }
+ const auto messages = parser.diagnosticMessages();
+ for (const DiagnosticMessage &msg : messages) {
+ ErrorMessage err = domParsingErrors().errorMessage(msg);
+ err.location.offset -= m_localOffset.offset;
+ err.location.startLine -= m_localOffset.startLine;
+ if (err.location.startLine == 1)
+ err.location.startColumn -= m_localOffset.startColumn;
+ addErrorLocal(std::move(err));
+ }
+ return parser.rootNode();
+}
+
+void ScriptExpression::astDumper(const Sink &s, AstDumperOptions options) const
+{
+ astNodeDumper(s, ast(), options, 1, 0, [this](SourceLocation astL) {
+ SourceLocation l = this->locationToLocal(astL);
+ return this->code().mid(l.offset, l.length);
+ });
+}
+
+QString ScriptExpression::astRelocatableDump() const
+{
+ return dumperToString([this](const Sink &s) {
+ this->astDumper(s, AstDumperOption::NoLocations | AstDumperOption::SloppyCompare);
+ });
+}
+
+void ScriptExpression::writeOut(const DomItem &self, OutWriter &lw) const
+{
+ OutWriter *ow = &lw;
+
+ std::optional<PendingSourceLocationId> codeLoc;
+ if (lw.lineWriter.options().updateOptions & LineWriterOptions::Update::Expressions)
+ codeLoc = lw.lineWriter.startSourceLocation([this, self, ow](SourceLocation myLoc) mutable {
+ QStringView reformattedCode =
+ QStringView(ow->writtenStr).mid(myLoc.offset, myLoc.length);
+ if (reformattedCode != code()) {
+ // If some reformatting of the expression took place,
+ // it will be saved as an intermediate step.
+ // then it will be used to restore writtenOut fileItem
+ // in the OutWriter::restoreWrittenFile
+
+ //Interestingly enough, this copyWithUpdatedCode will
+ //instantiate Engine and Parser and will parse "reformattedCode"
+ //because it calls ScriptExpression::setCode function
+ std::shared_ptr<ScriptExpression> copy =
+ copyWithUpdatedCode(self, reformattedCode.toString());
+ ow->addReformattedScriptExpression(self.canonicalPath(), copy);
+ }
+ });
+ reformatAst(
+ lw, m_astComments,
+ [this](SourceLocation astL) {
+ SourceLocation l = this->locationToLocal(astL); // use engine->code() instead?
+ return this->code().mid(l.offset, l.length);
+ },
+ ast());
+ if (codeLoc)
+ lw.lineWriter.endSourceLocation(*codeLoc);
+}
+
+SourceLocation ScriptExpression::globalLocation(const DomItem &self) const
+{
+ if (const FileLocations::Tree tree = FileLocations::treeOf(self)) {
+ return FileLocations::region(tree, MainRegion);
+ }
+ return SourceLocation();
+}
+
+bool PropertyDefinition::isParametricType() const
+{
+ return typeName.contains(QChar(u'<'));
+}
+
+void PropertyDefinition::writeOut(const DomItem &, OutWriter &lw) const
+{
+ lw.ensureNewline();
+ if (isDefaultMember)
+ lw.writeRegion(DefaultKeywordRegion).space();
+ if (isRequired)
+ lw.writeRegion(RequiredKeywordRegion).space();
+ if (isReadonly)
+ lw.writeRegion(ReadonlyKeywordRegion).space();
+ if (!typeName.isEmpty()) {
+ lw.writeRegion(PropertyKeywordRegion).space();
+ lw.writeRegion(TypeIdentifierRegion, typeName).space();
+ }
+ lw.writeRegion(IdentifierRegion, name);
+}
+
+bool MethodInfo::iterateDirectSubpaths(const DomItem &self, DirectVisitor visitor) const
+{
+ bool cont = AttributeInfo::iterateDirectSubpaths(self, visitor);
+ cont = cont && self.dvWrapField(visitor, Fields::parameters, parameters);
+ cont = cont && self.dvValueField(visitor, Fields::methodType, int(methodType));
+ if (!typeName.isEmpty())
+ cont = cont && self.dvReferenceField(visitor, Fields::type, typePath(self));
+ if (methodType == MethodType::Method) {
+ cont = cont && self.dvValueField(visitor, Fields::preCode, preCode(self));
+ cont = cont && self.dvValueField(visitor, Fields::postCode, postCode(self));
+ cont = cont && self.dvValueField(visitor, Fields::isConstructor, isConstructor);
+ }
+ if (returnType)
+ cont = cont && self.dvItemField(visitor, Fields::returnType, [this, &self]() {
+ return self.subOwnerItem(PathEls::Field(Fields::returnType), returnType);
+ });
+ if (body)
+ cont = cont && self.dvItemField(visitor, Fields::body, [this, &self]() {
+ return self.subOwnerItem(PathEls::Field(Fields::body), body);
+ });
+ return cont;
+}
+
+QString MethodInfo::preCode(const DomItem &self) const
+{
+ QString res;
+ LineWriter lw([&res](QStringView s) { res.append(s); }, QLatin1String("*preCode*"));
+ OutWriter ow(lw);
+ ow.indentNextlines = true;
+ ow.skipComments = true;
+ MockObject standinObj(self.pathFromOwner());
+ DomItem standin = self.copy(&standinObj);
+ ow.itemStart(standin);
+ ow.writeRegion(FunctionKeywordRegion).space().writeRegion(IdentifierRegion, name);
+ bool first = true;
+ ow.writeRegion(LeftParenthesisRegion);
+ for (const MethodParameter &mp : parameters) {
+ if (first)
+ first = false;
+ else
+ ow.write(u", ");
+ ow.write(mp.value->code());
+ }
+ ow.writeRegion(RightParenthesisRegion);
+ ow.ensureSpace().writeRegion(LeftBraceRegion);
+ ow.itemEnd(standin);
+ ow.eof();
+ return res;
+}
+
+QString MethodInfo::postCode(const DomItem &) const
+{
+ return QLatin1String("\n}\n");
+}
+
+void MethodInfo::writeOut(const DomItem &self, OutWriter &ow) const
+{
+ switch (methodType) {
+ case MethodType::Signal: {
+ if (body)
+ qCWarning(domLog) << "signal should not have a body in" << self.canonicalPath();
+ ow.writeRegion(SignalKeywordRegion).space().writeRegion(IdentifierRegion, name);
+ if (parameters.isEmpty())
+ return;
+ bool first = true;
+ ow.writeRegion(LeftParenthesisRegion);
+ int baseIndent = ow.increaseIndent();
+ for (const DomItem &arg : self.field(Fields::parameters).values()) {
+ if (first)
+ first = false;
+ else
+ ow.write(u", ");
+ if (const MethodParameter *argPtr = arg.as<MethodParameter>())
+ argPtr->writeOutSignal(arg, ow);
+ else
+ qCWarning(domLog) << "failed to cast to MethodParameter";
+ }
+ ow.writeRegion(RightParenthesisRegion);
+ ow.decreaseIndent(1, baseIndent);
+ return;
+ } break;
+ case MethodType::Method: {
+ ow.writeRegion(FunctionKeywordRegion).space().writeRegion(IdentifierRegion, name);
+ bool first = true;
+ ow.writeRegion(LeftParenthesisRegion);
+ for (const DomItem &arg : self.field(Fields::parameters).values()) {
+ if (first)
+ first = false;
+ else
+ ow.write(u", ");
+ arg.writeOut(ow);
+ }
+ ow.writeRegion(RightParenthesisRegion);
+ if (!typeName.isEmpty()) {
+ ow.writeRegion(ColonTokenRegion);
+ ow.space();
+ ow.writeRegion(TypeIdentifierRegion, typeName);
+ }
+ ow.ensureSpace().writeRegion(LeftBraceRegion);
+ int baseIndent = ow.increaseIndent();
+ if (DomItem b = self.field(Fields::body)) {
+ ow.ensureNewline();
+ b.writeOut(ow);
+ }
+ ow.decreaseIndent(1, baseIndent);
+ ow.ensureNewline().writeRegion(RightBraceRegion);
+ } break;
+ }
+}
+
+bool MethodParameter::iterateDirectSubpaths(const DomItem &self, DirectVisitor visitor) const
+{
+ bool cont = true;
+ cont = cont && self.dvValueField(visitor, Fields::name, name);
+ if (!typeName.isEmpty()) {
+ cont = cont
+ && self.dvReferenceField(visitor, Fields::type, Paths::lookupTypePath(typeName));
+ cont = cont && self.dvValueField(visitor, Fields::typeName, typeName);
+ }
+ cont = cont && self.dvValueField(visitor, Fields::isPointer, isPointer);
+ cont = cont && self.dvValueField(visitor, Fields::isReadonly, isReadonly);
+ cont = cont && self.dvValueField(visitor, Fields::isList, isList);
+ cont = cont && self.dvWrapField(visitor, Fields::defaultValue, defaultValue);
+ cont = cont && self.dvWrapField(visitor, Fields::value, value);
+
+ cont = cont && self.dvValueField(visitor, Fields::preCode, u"function f("_s);
+ cont = cont && self.dvValueField(visitor, Fields::postCode, u") {}"_s);
+
+ if (!annotations.isEmpty())
+ cont = cont && self.dvWrapField(visitor, Fields::annotations, annotations);
+ cont = cont && self.dvWrapField(visitor, Fields::comments, comments);
+ return cont;
+}
+
+void MethodParameter::writeOut(const DomItem &self, OutWriter &ow) const
+{
+ if (!name.isEmpty()) {
+ if (isRestElement)
+ ow.writeRegion(EllipsisTokenRegion);
+ ow.writeRegion(IdentifierRegion, name);
+ if (!typeName.isEmpty())
+ ow.writeRegion(ColonTokenRegion).space().writeRegion(TypeIdentifierRegion, typeName);
+ if (defaultValue) {
+ ow.space().writeRegion(EqualTokenRegion).space();
+ self.subOwnerItem(PathEls::Field(Fields::defaultValue), defaultValue).writeOut(ow);
+ }
+ } else {
+ if (value) {
+ self.subOwnerItem(PathEls::Field(Fields::value), value).writeOut(ow);
+ }
+ }
+}
+
+void MethodParameter::writeOutSignal(const DomItem &self, OutWriter &ow) const
+{
+ self.writeOutPre(ow);
+ if (!typeName.isEmpty())
+ ow.writeRegion(TypeIdentifierRegion, typeName).space();
+ ow.writeRegion(IdentifierRegion, name);
+ self.writeOutPost(ow);
+}
+
+void Pragma::writeOut(const DomItem &, OutWriter &ow) const
+{
+ ow.ensureNewline();
+ ow.writeRegion(PragmaKeywordRegion).space().writeRegion(IdentifierRegion, name);
+
+ bool isFirst = true;
+ for (const auto &value : values) {
+ if (isFirst) {
+ isFirst = false;
+ ow.writeRegion(ColonTokenRegion).space();
+ ow.writeRegion(PragmaValuesRegion, value);
+ continue;
+ }
+
+ ow.writeRegion(CommaTokenRegion).space();
+ ow.writeRegion(PragmaValuesRegion, value);
+ }
+ ow.ensureNewline();
+}
+
+bool EnumItem::iterateDirectSubpaths(const DomItem &self, DirectVisitor visitor) const
+{
+ bool cont = true;
+ cont = cont && self.dvValueField(visitor, Fields::name, name());
+ cont = cont && self.dvValueField(visitor, Fields::value, value());
+ cont = cont && self.dvWrapField(visitor, Fields::comments, m_comments);
+ return cont;
+}
+
+void EnumItem::writeOut(const DomItem &self, OutWriter &ow) const
+{
+ ow.ensureNewline();
+ ow.writeRegion(IdentifierRegion, name());
+ bool hasDefaultValue = false;
+ index_type myIndex = self.pathFromOwner().last().headIndex();
+ if (myIndex == 0)
+ hasDefaultValue = value() == 0;
+ else if (myIndex > 0)
+ hasDefaultValue = value()
+ == self.container()
+ .index(myIndex - 1)
+ .field(Fields::value)
+ .value()
+ .toDouble(value())
+ + 1;
+ if (!hasDefaultValue) {
+ QString v = QString::number(value(), 'f', 0);
+ if (abs(value() - v.toDouble()) > 1.e-10)
+ v = QString::number(value());
+ ow.space().writeRegion(EqualTokenRegion).space().writeRegion(PragmaValuesRegion, v);
+ }
+ if (myIndex >= 0 && self.container().indexes() != myIndex + 1)
+ ow.writeRegion(CommaTokenRegion);
+}
+
+QmlUri QmlUri::fromString(const QString &str)
+{
+ if (str.startsWith(u'"'))
+ return fromDirectoryString(str.mid(1, str.size() - 2)
+ .replace(u"\\\""_s, u"\""_s)
+ .replace(u"\\\\"_s, u"\\"_s));
+ else
+ return fromUriString(str);
+}
+
+QmlUri QmlUri::fromUriString(const QString &str)
+{
+ QRegularExpression moduleUriRe(QLatin1String(R"(\A\w+(?:\.\w+)*\Z)"));
+ return QmlUri((moduleUriRe.match(str).hasMatch() ? Kind::ModuleUri : Kind::Invalid), str);
+}
+
+QmlUri QmlUri::fromDirectoryString(const QString &str)
+{
+ QUrl url(str);
+ if (url.isValid() && url.scheme().size() > 1)
+ return QmlUri(url);
+ if (!str.isEmpty()) {
+ QFileInfo path(str);
+ return QmlUri((path.isRelative() ? Kind::RelativePath : Kind::AbsolutePath), str);
+ }
+ return {};
+}
+
+bool QmlUri::isValid() const
+{
+ return m_kind != Kind::Invalid;
+}
+
+bool QmlUri::isDirectory() const
+{
+ switch (m_kind) {
+ case Kind::Invalid:
+ case Kind::ModuleUri:
+ break;
+ case Kind::DirectoryUrl:
+ case Kind::RelativePath:
+ case Kind::AbsolutePath:
+ return true;
+ }
+ return false;
+}
+
+bool QmlUri::isModule() const
+{
+ return m_kind == Kind::ModuleUri;
+}
+
+QString QmlUri::moduleUri() const
+{
+ if (m_kind == Kind::ModuleUri)
+ return std::get<QString>(m_value);
+ return QString();
+}
+
+QString QmlUri::localPath() const
+{
+ switch (m_kind) {
+ case Kind::Invalid:
+ case Kind::ModuleUri:
+ break;
+ case Kind::DirectoryUrl: {
+ const QUrl &url = std::get<QUrl>(m_value);
+ if (url.scheme().compare(u"file", Qt::CaseInsensitive) == 0)
+ return url.path();
+ break;
+ }
+ case Kind::RelativePath:
+ case Kind::AbsolutePath:
+ return std::get<QString>(m_value);
+ }
+ return QString();
+}
+
+QString QmlUri::absoluteLocalPath(const QString &basePath) const
+{
+ switch (m_kind) {
+ case Kind::Invalid:
+ case Kind::ModuleUri:
+ break;
+ case Kind::DirectoryUrl: {
+ const QUrl &url = std::get<QUrl>(m_value);
+ if (url.scheme().compare(u"file", Qt::CaseInsensitive) == 0)
+ return url.path();
+ break;
+ }
+ case Kind::RelativePath: {
+ if (!basePath.isEmpty())
+ return QDir(basePath).filePath(std::get<QString>(m_value));
+ break;
+ }
+ case Kind::AbsolutePath:
+ return std::get<QString>(m_value);
+ }
+ return QString();
+}
+
+QUrl QmlUri::directoryUrl() const
+{
+ if (m_kind == Kind::DirectoryUrl)
+ return std::get<QUrl>(m_value);
+ return QUrl {};
+}
+
+QString QmlUri::directoryString() const
+{
+ switch (m_kind) {
+ case Kind::Invalid:
+ case Kind::ModuleUri:
+ break;
+ case Kind::DirectoryUrl:
+ return std::get<QUrl>(m_value).toString(); // set formatting? options?
+ case Kind::RelativePath:
+ case Kind::AbsolutePath:
+ return std::get<QString>(m_value);
+ }
+ return QString();
+}
+
+QString QmlUri::toString() const
+{
+ switch (m_kind) {
+ case Kind::Invalid:
+ break;
+ case Kind::ModuleUri:
+ return std::get<QString>(m_value);
+ case Kind::DirectoryUrl:
+ case Kind::RelativePath:
+ case Kind::AbsolutePath:
+ return u"\""_s + directoryString().replace(u'\\', u"\\\\"_s).replace(u'"', u"\\\""_s)
+ + u"\""_s;
+ }
+ return QString();
+}
+
+QmlUri::Kind QmlUri::kind() const
+{
+ return m_kind;
+}
+
+void ScriptExpression::setScriptElement(const ScriptElementVariant &p)
+{
+ m_element = p;
+}
+
+} // end namespace Dom
+} // end namespace QQmlJS
+
+QT_END_NAMESPACE
+
+#include "moc_qqmldomelements_p.cpp"
diff --git a/src/qmldom/qqmldomelements_p.h b/src/qmldom/qqmldomelements_p.h
new file mode 100644
index 0000000000..db57da7bb2
--- /dev/null
+++ b/src/qmldom/qqmldomelements_p.h
@@ -0,0 +1,1272 @@
+// Copyright (C) 2021 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+
+#ifndef QQMLDOMELEMENTS_P_H
+#define QQMLDOMELEMENTS_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 "qqmldomitem_p.h"
+#include "qqmldomconstants_p.h"
+#include "qqmldomcomments_p.h"
+#include "qqmldomlinewriter_p.h"
+
+#include <QtQml/private/qqmljsast_p.h>
+#include <QtQml/private/qqmljsengine_p.h>
+#include <QtQml/private/qqmlsignalnames_p.h>
+
+#include <QtCore/QCborValue>
+#include <QtCore/QCborMap>
+#include <QtCore/QMutexLocker>
+#include <QtCore/QPair>
+
+#include <memory>
+#include <private/qqmljsscope_p.h>
+
+#include <functional>
+#include <limits>
+
+QT_BEGIN_NAMESPACE
+
+namespace QQmlJS {
+namespace Dom {
+
+// namespace for utility methods building specific paths
+// using a namespace one can reopen it and add more methods in other places
+namespace Paths {
+Path moduleIndexPath(
+ const QString &uri, int majorVersion, const ErrorHandler &errorHandler = nullptr);
+Path moduleScopePath(
+ const QString &uri, Version version, const ErrorHandler &errorHandler = nullptr);
+Path moduleScopePath(
+ const QString &uri, const QString &version, const ErrorHandler &errorHandler = nullptr);
+inline Path moduleScopePath(
+ const QString &uri, const ErrorHandler &errorHandler = nullptr)
+{
+ return moduleScopePath(uri, QString(), errorHandler);
+}
+inline Path qmlDirInfoPath(const QString &path)
+{
+ return Path::Root(PathRoot::Top).field(Fields::qmldirWithPath).key(path);
+}
+inline Path qmlDirPath(const QString &path)
+{
+ return qmlDirInfoPath(path).field(Fields::currentItem);
+}
+inline Path qmldirFileInfoPath(const QString &path)
+{
+ return Path::Root(PathRoot::Top).field(Fields::qmldirFileWithPath).key(path);
+}
+inline Path qmldirFilePath(const QString &path)
+{
+ return qmldirFileInfoPath(path).field(Fields::currentItem);
+}
+inline Path qmlFileInfoPath(const QString &canonicalFilePath)
+{
+ return Path::Root(PathRoot::Top).field(Fields::qmlFileWithPath).key(canonicalFilePath);
+}
+inline Path qmlFilePath(const QString &canonicalFilePath)
+{
+ return qmlFileInfoPath(canonicalFilePath).field(Fields::currentItem);
+}
+inline Path qmlFileObjectPath(const QString &canonicalFilePath)
+{
+ return qmlFilePath(canonicalFilePath)
+ .field(Fields::components)
+ .key(QString())
+ .index(0)
+ .field(Fields::objects)
+ .index(0);
+}
+inline Path qmltypesFileInfoPath(const QString &path)
+{
+ return Path::Root(PathRoot::Top).field(Fields::qmltypesFileWithPath).key(path);
+}
+inline Path qmltypesFilePath(const QString &path)
+{
+ return qmltypesFileInfoPath(path).field(Fields::currentItem);
+}
+inline Path jsFileInfoPath(const QString &path)
+{
+ return Path::Root(PathRoot::Top).field(Fields::jsFileWithPath).key(path);
+}
+inline Path jsFilePath(const QString &path)
+{
+ return jsFileInfoPath(path).field(Fields::currentItem);
+}
+inline Path qmlDirectoryInfoPath(const QString &path)
+{
+ return Path::Root(PathRoot::Top).field(Fields::qmlDirectoryWithPath).key(path);
+}
+inline Path qmlDirectoryPath(const QString &path)
+{
+ return qmlDirectoryInfoPath(path).field(Fields::currentItem);
+}
+inline Path globalScopeInfoPath(const QString &name)
+{
+ return Path::Root(PathRoot::Top).field(Fields::globalScopeWithName).key(name);
+}
+inline Path globalScopePath(const QString &name)
+{
+ return globalScopeInfoPath(name).field(Fields::currentItem);
+}
+inline Path lookupCppTypePath(const QString &name)
+{
+ return Path::Current(PathCurrent::Lookup).field(Fields::cppType).key(name);
+}
+inline Path lookupPropertyPath(const QString &name)
+{
+ return Path::Current(PathCurrent::Lookup).field(Fields::propertyDef).key(name);
+}
+inline Path lookupSymbolPath(const QString &name)
+{
+ return Path::Current(PathCurrent::Lookup).field(Fields::symbol).key(name);
+}
+inline Path lookupTypePath(const QString &name)
+{
+ return Path::Current(PathCurrent::Lookup).field(Fields::type).key(name);
+}
+inline Path loadInfoPath(const Path &el)
+{
+ return Path::Root(PathRoot::Env).field(Fields::loadInfo).key(el.toString());
+}
+} // end namespace Paths
+
+class QMLDOM_EXPORT CommentableDomElement : public DomElement
+{
+public:
+ CommentableDomElement(const Path &pathFromOwner = Path()) : DomElement(pathFromOwner) { }
+ CommentableDomElement(const CommentableDomElement &o) : DomElement(o), m_comments(o.m_comments)
+ {
+ }
+ CommentableDomElement &operator=(const CommentableDomElement &o) = default;
+ bool iterateDirectSubpaths(const DomItem &self, DirectVisitor) const override;
+ RegionComments &comments() { return m_comments; }
+ const RegionComments &comments() const { return m_comments; }
+
+private:
+ RegionComments m_comments;
+};
+
+class QMLDOM_EXPORT Version
+{
+public:
+ constexpr static DomType kindValue = DomType::Version;
+ constexpr static qint32 Undefined = -1;
+ constexpr static qint32 Latest = -2;
+
+ Version(qint32 majorVersion = Undefined, qint32 minorVersion = Undefined);
+ static Version fromString(QStringView v);
+
+ bool iterateDirectSubpaths(const DomItem &self, DirectVisitor) const;
+
+ bool isLatest() const;
+ bool isValid() const;
+ QString stringValue() const;
+ QString majorString() const
+ {
+ if (majorVersion >= 0 || majorVersion == Undefined)
+ return QString::number(majorVersion);
+ return QString();
+ }
+ QString majorSymbolicString() const
+ {
+ if (majorVersion == Version::Latest)
+ return QLatin1String("Latest");
+ if (majorVersion >= 0 || majorVersion == Undefined)
+ return QString::number(majorVersion);
+ return QString();
+ }
+ QString minorString() const
+ {
+ if (minorVersion >= 0 || minorVersion == Undefined)
+ return QString::number(minorVersion);
+ return QString();
+ }
+ int compare(const Version &o) const
+ {
+ int c = majorVersion - o.majorVersion;
+ if (c != 0)
+ return c;
+ return minorVersion - o.minorVersion;
+ }
+
+ qint32 majorVersion;
+ qint32 minorVersion;
+};
+inline bool operator==(const Version &v1, const Version &v2)
+{
+ return v1.compare(v2) == 0;
+}
+inline bool operator!=(const Version &v1, const Version &v2)
+{
+ return v1.compare(v2) != 0;
+}
+inline bool operator<(const Version &v1, const Version &v2)
+{
+ return v1.compare(v2) < 0;
+}
+inline bool operator<=(const Version &v1, const Version &v2)
+{
+ return v1.compare(v2) <= 0;
+}
+inline bool operator>(const Version &v1, const Version &v2)
+{
+ return v1.compare(v2) > 0;
+}
+inline bool operator>=(const Version &v1, const Version &v2)
+{
+ return v1.compare(v2) >= 0;
+}
+
+class QMLDOM_EXPORT QmlUri
+{
+public:
+ enum class Kind { Invalid, ModuleUri, DirectoryUrl, RelativePath, AbsolutePath };
+ QmlUri() = default;
+ static QmlUri fromString(const QString &importStr);
+ static QmlUri fromUriString(const QString &importStr);
+ static QmlUri fromDirectoryString(const QString &importStr);
+ bool isValid() const;
+ bool isDirectory() const;
+ bool isModule() const;
+ QString moduleUri() const;
+ QString localPath() const;
+ QString absoluteLocalPath(const QString &basePath = QString()) const;
+ QUrl directoryUrl() const;
+ QString directoryString() const;
+ QString toString() const;
+ Kind kind() const;
+
+ friend bool operator==(const QmlUri &i1, const QmlUri &i2)
+ {
+ return i1.m_kind == i2.m_kind && i1.m_value == i2.m_value;
+ }
+ friend bool operator!=(const QmlUri &i1, const QmlUri &i2) { return !(i1 == i2); }
+
+private:
+ QmlUri(const QUrl &url) : m_kind(Kind::DirectoryUrl), m_value(url) { }
+ QmlUri(Kind kind, const QString &value) : m_kind(kind), m_value(value) { }
+ Kind m_kind = Kind::Invalid;
+ std::variant<QString, QUrl> m_value;
+};
+
+class QMLDOM_EXPORT Import
+{
+ Q_DECLARE_TR_FUNCTIONS(Import)
+public:
+ constexpr static DomType kindValue = DomType::Import;
+
+ static Import fromUriString(
+ const QString &importStr, Version v = Version(), const QString &importId = QString(),
+ const ErrorHandler &handler = nullptr);
+ static Import fromFileString(
+ const QString &importStr, const QString &importId = QString(),
+ const ErrorHandler &handler = nullptr);
+
+ Import(const QmlUri &uri = QmlUri(), Version version = Version(),
+ const QString &importId = QString())
+ : uri(uri), version(version), importId(importId)
+ {
+ }
+
+ bool iterateDirectSubpaths(const DomItem &self, DirectVisitor) const;
+ Path importedPath() const
+ {
+ if (uri.isDirectory()) {
+ QString path = uri.absoluteLocalPath();
+ if (!path.isEmpty()) {
+ return Paths::qmlDirPath(path);
+ } else {
+ Q_ASSERT_X(false, "Import", "url imports not supported");
+ return Paths::qmldirFilePath(uri.directoryString());
+ }
+ } else {
+ return Paths::moduleScopePath(uri.moduleUri(), version);
+ }
+ }
+ Import baseImport() const { return Import { uri, version }; }
+
+ friend bool operator==(const Import &i1, const Import &i2)
+ {
+ return i1.uri == i2.uri && i1.version == i2.version && i1.importId == i2.importId
+ && i1.comments == i2.comments && i1.implicit == i2.implicit;
+ }
+ friend bool operator!=(const Import &i1, const Import &i2) { return !(i1 == i2); }
+
+ void writeOut(const DomItem &self, OutWriter &ow) const;
+
+ static QRegularExpression importRe();
+
+ QmlUri uri;
+ Version version;
+ QString importId;
+ RegionComments comments;
+ bool implicit = false;
+};
+
+class QMLDOM_EXPORT ModuleAutoExport
+{
+public:
+ constexpr static DomType kindValue = DomType::ModuleAutoExport;
+
+ bool iterateDirectSubpaths(const DomItem &self, DirectVisitor visitor) const
+ {
+ bool cont = true;
+ cont = cont && self.dvWrapField(visitor, Fields::import, import);
+ cont = cont && self.dvValueField(visitor, Fields::inheritVersion, inheritVersion);
+ return cont;
+ }
+
+ friend bool operator==(const ModuleAutoExport &i1, const ModuleAutoExport &i2)
+ {
+ return i1.import == i2.import && i1.inheritVersion == i2.inheritVersion;
+ }
+ friend bool operator!=(const ModuleAutoExport &i1, const ModuleAutoExport &i2)
+ {
+ return !(i1 == i2);
+ }
+
+ Import import;
+ bool inheritVersion = false;
+};
+
+class QMLDOM_EXPORT Pragma
+{
+public:
+ constexpr static DomType kindValue = DomType::Pragma;
+
+ Pragma(const QString &pragmaName = QString(), const QStringList &pragmaValues = {})
+ : name(pragmaName), values{ pragmaValues }
+ {
+ }
+
+ bool iterateDirectSubpaths(const DomItem &self, DirectVisitor visitor) const
+ {
+ bool cont = self.dvValueField(visitor, Fields::name, name);
+ cont = cont && self.dvValueField(visitor, Fields::values, values);
+ cont = cont && self.dvWrapField(visitor, Fields::comments, comments);
+ return cont;
+ }
+
+ void writeOut(const DomItem &self, OutWriter &ow) const;
+
+ QString name;
+ QStringList values;
+ RegionComments comments;
+};
+
+class QMLDOM_EXPORT Id
+{
+public:
+ constexpr static DomType kindValue = DomType::Id;
+
+ Id(const QString &idName = QString(), const Path &referredObject = Path());
+
+ bool iterateDirectSubpaths(const DomItem &self, DirectVisitor) const;
+ void updatePathFromOwner(const Path &pathFromOwner);
+ Path addAnnotation(const Path &selfPathFromOwner, const QmlObject &ann, QmlObject **aPtr = nullptr);
+
+ QString name;
+ Path referredObjectPath;
+ RegionComments comments;
+ QList<QmlObject> annotations;
+ std::shared_ptr<ScriptExpression> value;
+};
+
+// TODO: rename? it may contain statements and stuff, not only expressions
+// TODO QTBUG-121933
+class QMLDOM_EXPORT ScriptExpression final : public OwningItem
+{
+ Q_GADGET
+ Q_DECLARE_TR_FUNCTIONS(ScriptExpression)
+public:
+ enum class ExpressionType {
+ BindingExpression,
+ FunctionBody,
+ ArgInitializer,
+ ArgumentStructure,
+ ReturnType,
+ JSCode, // Used for storing the content of the whole .js file as "one" Expression
+ ESMCode, // Used for storing the content of the whole ECMAScript module (.mjs) as "one"
+ // Expression
+ };
+ Q_ENUM(ExpressionType);
+ constexpr static DomType kindValue = DomType::ScriptExpression;
+ DomType kind() const override { return kindValue; }
+
+ explicit ScriptExpression(
+ QStringView code, const std::shared_ptr<QQmlJS::Engine> &engine, AST::Node *ast,
+ const std::shared_ptr<AstComments> &comments, ExpressionType expressionType,
+ SourceLocation localOffset = SourceLocation(), int derivedFrom = 0,
+ QStringView preCode = QStringView(), QStringView postCode = QStringView());
+
+ ScriptExpression()
+ : ScriptExpression(QStringView(), std::shared_ptr<QQmlJS::Engine>(), nullptr,
+ std::shared_ptr<AstComments>(), ExpressionType::BindingExpression,
+ SourceLocation(), 0)
+ {
+ }
+
+ explicit ScriptExpression(
+ const QString &code, ExpressionType expressionType, int derivedFrom = 0,
+ const QString &preCode = QString(), const QString &postCode = QString())
+ : OwningItem(derivedFrom), m_expressionType(expressionType)
+ {
+ setCode(code, preCode, postCode);
+ }
+
+ ScriptExpression(const ScriptExpression &e);
+
+ std::shared_ptr<ScriptExpression> makeCopy(const DomItem &self) const
+ {
+ return std::static_pointer_cast<ScriptExpression>(doCopy(self));
+ }
+
+ std::shared_ptr<ScriptExpression> copyWithUpdatedCode(const DomItem &self, const QString &code) const;
+
+ bool iterateDirectSubpaths(const DomItem &self, DirectVisitor visitor) const override;
+
+ Path canonicalPath(const DomItem &self) const override { return self.m_ownerPath; }
+ // parsed and created if not available
+ AST::Node *ast() const { return m_ast; }
+ // dump of the ast (without locations)
+ void astDumper(const Sink &s, AstDumperOptions options) const;
+ QString astRelocatableDump() const;
+
+ // definedSymbols name, value, from
+ // usedSymbols name, locations
+ QStringView code() const
+ {
+ QMutexLocker l(mutex());
+ return m_code;
+ }
+
+ ExpressionType expressionType() const
+ {
+ QMutexLocker l(mutex());
+ return m_expressionType;
+ }
+
+ bool isNull() const
+ {
+ QMutexLocker l(mutex());
+ return m_code.isNull();
+ }
+ std::shared_ptr<QQmlJS::Engine> engine() const
+ {
+ QMutexLocker l(mutex());
+ return m_engine;
+ }
+ std::shared_ptr<AstComments> astComments() const { return m_astComments; }
+ void writeOut(const DomItem &self, OutWriter &lw) const override;
+ SourceLocation globalLocation(const DomItem &self) const;
+ SourceLocation localOffset() const { return m_localOffset; }
+ QStringView preCode() const { return m_preCode; }
+ QStringView postCode() const { return m_postCode; }
+ void setScriptElement(const ScriptElementVariant &p);
+ ScriptElementVariant scriptElement() { return m_element; }
+
+protected:
+ std::shared_ptr<OwningItem> doCopy(const DomItem &) const override
+ {
+ return std::make_shared<ScriptExpression>(*this);
+ }
+
+ std::function<SourceLocation(SourceLocation)> locationToGlobalF(const DomItem &self) const
+ {
+ SourceLocation loc = globalLocation(self);
+ return [loc, this](SourceLocation x) {
+ return SourceLocation(x.offset - m_localOffset.offset + loc.offset, x.length,
+ x.startLine - m_localOffset.startLine + loc.startLine,
+ ((x.startLine == m_localOffset.startLine) ? x.startColumn
+ - m_localOffset.startColumn + loc.startColumn
+ : x.startColumn));
+ };
+ }
+
+ SourceLocation locationToLocal(SourceLocation x) const
+ {
+ return SourceLocation(
+ x.offset - m_localOffset.offset, x.length, x.startLine - m_localOffset.startLine,
+ ((x.startLine == m_localOffset.startLine)
+ ? x.startColumn - m_localOffset.startColumn
+ : x.startColumn)); // are line and column 1 based? then we should + 1
+ }
+
+ std::function<SourceLocation(SourceLocation)> locationToLocalF(const DomItem &) const
+ {
+ return [this](SourceLocation x) { return locationToLocal(x); };
+ }
+
+private:
+ enum class ParseMode {
+ QML,
+ JS,
+ ESM, // ECMAScript module
+ };
+
+ inline ParseMode resolveParseMode()
+ {
+ switch (m_expressionType) {
+ case ExpressionType::BindingExpression:
+ // unfortunately there are no documentation explaining this resolution
+ // this was just moved from the original implementation
+ return ParseMode::QML;
+ case ExpressionType::ESMCode:
+ return ParseMode::ESM;
+ default:
+ return ParseMode::JS;
+ }
+ }
+ void setCode(const QString &code, const QString &preCode, const QString &postCode);
+ [[nodiscard]] AST::Node *parse(ParseMode mode);
+
+ ExpressionType m_expressionType;
+ QString m_codeStr;
+ QStringView m_code;
+ QStringView m_preCode;
+ QStringView m_postCode;
+ mutable std::shared_ptr<QQmlJS::Engine> m_engine;
+ mutable AST::Node *m_ast;
+ std::shared_ptr<AstComments> m_astComments;
+ SourceLocation m_localOffset;
+ ScriptElementVariant m_element;
+};
+
+class BindingValue;
+
+class QMLDOM_EXPORT Binding
+{
+public:
+ constexpr static DomType kindValue = DomType::Binding;
+
+ Binding(const QString &m_name = QString(),
+ std::unique_ptr<BindingValue> value = std::unique_ptr<BindingValue>(),
+ BindingType bindingType = BindingType::Normal);
+ Binding(const QString &m_name, const std::shared_ptr<ScriptExpression> &value,
+ BindingType bindingType = BindingType::Normal);
+ Binding(const QString &m_name, const QString &scriptCode,
+ BindingType bindingType = BindingType::Normal);
+ Binding(const QString &m_name, const QmlObject &value,
+ BindingType bindingType = BindingType::Normal);
+ Binding(const QString &m_name, const QList<QmlObject> &value,
+ BindingType bindingType = BindingType::Normal);
+ Binding(const Binding &o);
+ Binding(Binding &&o) = default;
+ ~Binding();
+ Binding &operator=(const Binding &);
+ Binding &operator=(Binding &&) = default;
+
+ bool iterateDirectSubpaths(const DomItem &self, DirectVisitor) const;
+ DomItem valueItem(const DomItem &self) const; // ### REVISIT: consider replacing return value with variant
+ BindingValueKind valueKind() const;
+ QString name() const { return m_name; }
+ BindingType bindingType() const { return m_bindingType; }
+ QmlObject const *objectValue() const;
+ QList<QmlObject> const *arrayValue() const;
+ std::shared_ptr<ScriptExpression> scriptExpressionValue() const;
+ QmlObject *objectValue();
+ QList<QmlObject> *arrayValue();
+ std::shared_ptr<ScriptExpression> scriptExpressionValue();
+ QList<QmlObject> annotations() const { return m_annotations; }
+ void setAnnotations(const QList<QmlObject> &annotations) { m_annotations = annotations; }
+ void setValue(std::unique_ptr<BindingValue> &&value) { m_value = std::move(value); }
+ Path addAnnotation(const Path &selfPathFromOwner, const QmlObject &a, QmlObject **aPtr = nullptr);
+ const RegionComments &comments() const { return m_comments; }
+ RegionComments &comments() { return m_comments; }
+ void updatePathFromOwner(const Path &newPath);
+ void writeOut(const DomItem &self, OutWriter &lw) const;
+ void writeOutValue(const DomItem &self, OutWriter &lw) const;
+ bool isSignalHandler() const
+ {
+ QString baseName = m_name.split(QLatin1Char('.')).last();
+ return QQmlSignalNames::isHandlerName(baseName);
+ }
+ static QString preCodeForName(QStringView n)
+ {
+ return QStringLiteral(u"QtObject{\n %1: ").arg(n.split(u'.').last());
+ }
+ static QString postCodeForName(QStringView) { return QStringLiteral(u"\n}\n"); }
+ QString preCode() const { return preCodeForName(m_name); }
+ QString postCode() const { return postCodeForName(m_name); }
+
+ ScriptElementVariant bindingIdentifiers() const { return m_bindingIdentifiers; }
+ void setBindingIdentifiers(const ScriptElementVariant &bindingIdentifiers) { m_bindingIdentifiers = bindingIdentifiers; }
+
+private:
+ friend class QQmlDomAstCreator;
+ BindingType m_bindingType;
+ QString m_name;
+ std::unique_ptr<BindingValue> m_value;
+ QList<QmlObject> m_annotations;
+ RegionComments m_comments;
+ ScriptElementVariant m_bindingIdentifiers;
+};
+
+class QMLDOM_EXPORT AttributeInfo
+{
+public:
+ enum Access { Private, Protected, Public };
+
+ bool iterateDirectSubpaths(const DomItem &self, DirectVisitor visitor) const;
+
+ Path addAnnotation(const Path &selfPathFromOwner, const QmlObject &annotation,
+ QmlObject **aPtr = nullptr);
+ void updatePathFromOwner(const Path &newPath);
+
+ QQmlJSScope::ConstPtr semanticScope() const { return m_semanticScope; }
+ void setSemanticScope(const QQmlJSScope::ConstPtr &scope) { m_semanticScope = scope; }
+
+ QString name;
+ Access access = Access::Public;
+ QString typeName;
+ bool isReadonly = false;
+ bool isList = false;
+ QList<QmlObject> annotations;
+ RegionComments comments;
+ QQmlJSScope::ConstPtr m_semanticScope;
+};
+
+struct QMLDOM_EXPORT LocallyResolvedAlias
+{
+ enum class Status { Invalid, ResolvedProperty, ResolvedObject, Loop, TooDeep };
+ bool valid()
+ {
+ switch (status) {
+ case Status::ResolvedProperty:
+ case Status::ResolvedObject:
+ return true;
+ default:
+ return false;
+ }
+ }
+ DomItem baseObject;
+ DomItem localPropertyDef;
+ QString typeName;
+ QStringList accessedPath;
+ Status status = Status::Invalid;
+ int nAliases = 0;
+};
+
+class QMLDOM_EXPORT PropertyDefinition : public AttributeInfo
+{
+public:
+ constexpr static DomType kindValue = DomType::PropertyDefinition;
+
+ bool iterateDirectSubpaths(const DomItem &self, DirectVisitor visitor) const
+ {
+ bool cont = AttributeInfo::iterateDirectSubpaths(self, visitor);
+ cont = cont && self.dvValueField(visitor, Fields::isPointer, isPointer);
+ cont = cont && self.dvValueField(visitor, Fields::isFinal, isFinal);
+ cont = cont && self.dvValueField(visitor, Fields::isAlias, isAlias());
+ cont = cont && self.dvValueField(visitor, Fields::isDefaultMember, isDefaultMember);
+ cont = cont && self.dvValueField(visitor, Fields::isRequired, isRequired);
+ cont = cont && self.dvValueField(visitor, Fields::read, read);
+ cont = cont && self.dvValueField(visitor, Fields::write, write);
+ cont = cont && self.dvValueField(visitor, Fields::bindable, bindable);
+ cont = cont && self.dvValueField(visitor, Fields::notify, notify);
+ cont = cont && self.dvReferenceField(visitor, Fields::type, typePath());
+ if (m_nameIdentifiers) {
+ cont = cont && self.dvItemField(visitor, Fields::nameIdentifiers, [this, &self]() {
+ return self.subScriptElementWrapperItem(m_nameIdentifiers);
+ });
+ }
+ return cont;
+ }
+
+ Path typePath() const { return Paths::lookupTypePath(typeName); }
+
+ bool isAlias() const { return typeName == u"alias"; }
+ bool isParametricType() const;
+ void writeOut(const DomItem &self, OutWriter &lw) const;
+ ScriptElementVariant nameIdentifiers() const { return m_nameIdentifiers; }
+ void setNameIdentifiers(const ScriptElementVariant &name) { m_nameIdentifiers = name; }
+
+ QString read;
+ QString write;
+ QString bindable;
+ QString notify;
+ bool isFinal = false;
+ bool isPointer = false;
+ bool isDefaultMember = false;
+ bool isRequired = false;
+ ScriptElementVariant m_nameIdentifiers;
+};
+
+class QMLDOM_EXPORT PropertyInfo
+{
+public:
+ constexpr static DomType kindValue = DomType::PropertyInfo; // used to get the correct kind in ObjectWrapper
+
+ bool iterateDirectSubpaths(const DomItem &self, DirectVisitor visitor) const;
+
+ QList<DomItem> propertyDefs;
+ QList<DomItem> bindings;
+};
+
+class QMLDOM_EXPORT MethodParameter
+{
+public:
+ constexpr static DomType kindValue = DomType::MethodParameter;
+
+ bool iterateDirectSubpaths(const DomItem &self, DirectVisitor visitor) const;
+
+ void writeOut(const DomItem &self, OutWriter &ow) const;
+ void writeOutSignal(const DomItem &self, OutWriter &ow) const;
+
+ QString name;
+ QString typeName;
+ bool isPointer = false;
+ bool isReadonly = false;
+ bool isList = false;
+ bool isRestElement = false;
+ std::shared_ptr<ScriptExpression> defaultValue;
+ /*!
+ \internal
+ Contains the scriptElement representing this argument, inclusive default value,
+ deconstruction, etc.
+ */
+ std::shared_ptr<ScriptExpression> value;
+ QList<QmlObject> annotations;
+ RegionComments comments;
+};
+
+class QMLDOM_EXPORT MethodInfo : public AttributeInfo
+{
+ Q_GADGET
+public:
+ enum MethodType { Signal, Method };
+ Q_ENUM(MethodType)
+
+ constexpr static DomType kindValue = DomType::MethodInfo;
+
+ Path typePath(const DomItem &) const
+ {
+ return (typeName.isEmpty() ? Path() : Paths::lookupTypePath(typeName));
+ }
+
+ bool iterateDirectSubpaths(const DomItem &self, DirectVisitor visitor) const;
+ QString preCode(const DomItem &) const; // ### REVISIT, might be simplified by using different toplevel production rules at usage site
+ QString postCode(const DomItem &) const;
+ void writePre(const DomItem &self, OutWriter &ow) const;
+ void writeOut(const DomItem &self, OutWriter &ow) const;
+ void setCode(const QString &code)
+ {
+ body = std::make_shared<ScriptExpression>(
+ code, ScriptExpression::ExpressionType::FunctionBody, 0,
+ QLatin1String("function foo(){\n"), QLatin1String("\n}\n"));
+ }
+ MethodInfo() = default;
+
+ // TODO: make private + add getters/setters
+ QList<MethodParameter> parameters;
+ MethodType methodType = Method;
+ std::shared_ptr<ScriptExpression> body;
+ std::shared_ptr<ScriptExpression> returnType;
+ bool isConstructor = false;
+};
+
+class QMLDOM_EXPORT EnumItem
+{
+public:
+ constexpr static DomType kindValue = DomType::EnumItem;
+
+ EnumItem(const QString &name = QString(), int value = 0) : m_name(name), m_value(value) { }
+
+ bool iterateDirectSubpaths(const DomItem &self, DirectVisitor visitor) const;
+
+ QString name() const { return m_name; }
+ double value() const { return m_value; }
+ RegionComments &comments() { return m_comments; }
+ const RegionComments &comments() const { return m_comments; }
+ void writeOut(const DomItem &self, OutWriter &lw) const;
+
+private:
+ QString m_name;
+ double m_value;
+ RegionComments m_comments;
+};
+
+class QMLDOM_EXPORT EnumDecl final : public CommentableDomElement
+{
+public:
+ constexpr static DomType kindValue = DomType::EnumDecl;
+ DomType kind() const override { return kindValue; }
+
+ EnumDecl(const QString &name = QString(), QList<EnumItem> values = QList<EnumItem>(),
+ Path pathFromOwner = Path())
+ : CommentableDomElement(pathFromOwner), m_name(name), m_values(values)
+ {
+ }
+
+ bool iterateDirectSubpaths(const DomItem &self, DirectVisitor visitor) const override;
+
+ QString name() const { return m_name; }
+ void setName(const QString &name) { m_name = name; }
+ const QList<EnumItem> &values() const & { return m_values; }
+ bool isFlag() const { return m_isFlag; }
+ void setIsFlag(bool flag) { m_isFlag = flag; }
+ QString alias() const { return m_alias; }
+ void setAlias(const QString &aliasName) { m_alias = aliasName; }
+ void setValues(QList<EnumItem> values) { m_values = values; }
+ Path addValue(EnumItem value)
+ {
+ m_values.append(value);
+ return Path::Field(Fields::values).index(index_type(m_values.size() - 1));
+ }
+ void updatePathFromOwner(const Path &newP) override;
+
+ const QList<QmlObject> &annotations() const & { return m_annotations; }
+ void setAnnotations(const QList<QmlObject> &annotations);
+ Path addAnnotation(const QmlObject &child, QmlObject **cPtr = nullptr);
+ void writeOut(const DomItem &self, OutWriter &lw) const override;
+
+private:
+ QString m_name;
+ bool m_isFlag = false;
+ QString m_alias;
+ QList<EnumItem> m_values;
+ QList<QmlObject> m_annotations;
+};
+
+class QMLDOM_EXPORT QmlObject final : public CommentableDomElement
+{
+ Q_DECLARE_TR_FUNCTIONS(QmlObject)
+public:
+ constexpr static DomType kindValue = DomType::QmlObject;
+ DomType kind() const override { return kindValue; }
+
+ QmlObject(const Path &pathFromOwner = Path());
+ bool iterateDirectSubpaths(const DomItem &self, DirectVisitor) const override;
+ bool iterateBaseDirectSubpaths(const DomItem &self, DirectVisitor) const;
+ QList<QString> fields() const;
+ QList<QString> fields(const DomItem &) const override { return fields(); }
+ DomItem field(const DomItem &self, QStringView name) const override;
+ void updatePathFromOwner(const Path &newPath) override;
+ QString localDefaultPropertyName() const;
+ QString defaultPropertyName(const DomItem &self) const;
+ virtual bool iterateSubOwners(const DomItem &self, function_ref<bool(const DomItem &owner)> visitor) const;
+
+ QString idStr() const { return m_idStr; }
+ QString name() const { return m_name; }
+ const QList<Path> &prototypePaths() const & { return m_prototypePaths; }
+ Path nextScopePath() const { return m_nextScopePath; }
+ const QMultiMap<QString, PropertyDefinition> &propertyDefs() const & { return m_propertyDefs; }
+ const QMultiMap<QString, Binding> &bindings() const & { return m_bindings; }
+ const QMultiMap<QString, MethodInfo> &methods() const & { return m_methods; }
+ QList<QmlObject> children() const { return m_children; }
+ QList<QmlObject> annotations() const { return m_annotations; }
+
+ void setIdStr(const QString &id) { m_idStr = id; }
+ void setName(const QString &name) { m_name = name; }
+ void setDefaultPropertyName(const QString &name) { m_defaultPropertyName = name; }
+ void setPrototypePaths(QList<Path> prototypePaths) { m_prototypePaths = prototypePaths; }
+ Path addPrototypePath(const Path &prototypePath)
+ {
+ index_type idx = index_type(m_prototypePaths.indexOf(prototypePath));
+ if (idx == -1) {
+ idx = index_type(m_prototypePaths.size());
+ m_prototypePaths.append(prototypePath);
+ }
+ return Path::Field(Fields::prototypes).index(idx);
+ }
+ void setNextScopePath(const Path &nextScopePath) { m_nextScopePath = nextScopePath; }
+ void setPropertyDefs(QMultiMap<QString, PropertyDefinition> propertyDefs)
+ {
+ m_propertyDefs = propertyDefs;
+ }
+ void setBindings(QMultiMap<QString, Binding> bindings) { m_bindings = bindings; }
+ void setMethods(QMultiMap<QString, MethodInfo> functionDefs) { m_methods = functionDefs; }
+ void setChildren(const QList<QmlObject> &children)
+ {
+ m_children = children;
+ if (pathFromOwner())
+ updatePathFromOwner(pathFromOwner());
+ }
+ void setAnnotations(const QList<QmlObject> &annotations)
+ {
+ m_annotations = annotations;
+ if (pathFromOwner())
+ updatePathFromOwner(pathFromOwner());
+ }
+ Path addPropertyDef(const PropertyDefinition &propertyDef, AddOption option,
+ PropertyDefinition **pDef = nullptr)
+ {
+ return insertUpdatableElementInMultiMap(pathFromOwner().field(Fields::propertyDefs),
+ m_propertyDefs, propertyDef.name, propertyDef,
+ option, pDef);
+ }
+ MutableDomItem addPropertyDef(MutableDomItem &self, const PropertyDefinition &propertyDef,
+ AddOption option);
+
+ Path addBinding(Binding binding, AddOption option, Binding **bPtr = nullptr)
+ {
+ return insertUpdatableElementInMultiMap(pathFromOwner().field(Fields::bindings), m_bindings,
+ binding.name(), binding, option, bPtr);
+ }
+ MutableDomItem addBinding(MutableDomItem &self, Binding binding, AddOption option);
+ Path addMethod(const MethodInfo &functionDef, AddOption option, MethodInfo **mPtr = nullptr)
+ {
+ return insertUpdatableElementInMultiMap(pathFromOwner().field(Fields::methods), m_methods,
+ functionDef.name, functionDef, option, mPtr);
+ }
+ MutableDomItem addMethod(MutableDomItem &self, const MethodInfo &functionDef, AddOption option);
+ Path addChild(QmlObject child, QmlObject **cPtr = nullptr)
+ {
+ return appendUpdatableElementInQList(pathFromOwner().field(Fields::children), m_children,
+ child, cPtr);
+ }
+ MutableDomItem addChild(MutableDomItem &self, QmlObject child)
+ {
+ Path p = addChild(child);
+ return MutableDomItem(self.owner().item(), p);
+ }
+ Path addAnnotation(const QmlObject &annotation, QmlObject **aPtr = nullptr)
+ {
+ return appendUpdatableElementInQList(pathFromOwner().field(Fields::annotations),
+ m_annotations, annotation, aPtr);
+ }
+ void writeOut(const DomItem &self, OutWriter &ow, const QString &onTarget) const;
+ void writeOut(const DomItem &self, OutWriter &lw) const override { writeOut(self, lw, QString()); }
+
+ LocallyResolvedAlias resolveAlias(const DomItem &self,
+ std::shared_ptr<ScriptExpression> accessSequence) const;
+ LocallyResolvedAlias resolveAlias(const DomItem &self, const QStringList &accessSequence) const;
+
+ QQmlJSScope::ConstPtr semanticScope() const { return m_scope; }
+ void setSemanticScope(const QQmlJSScope::ConstPtr &scope) { m_scope = scope; }
+
+ ScriptElementVariant nameIdentifiers() const { return m_nameIdentifiers; }
+ void setNameIdentifiers(const ScriptElementVariant &name) { m_nameIdentifiers = name; }
+
+private:
+ friend class QQmlDomAstCreator;
+ QString m_idStr;
+ QString m_name;
+ QList<Path> m_prototypePaths;
+ Path m_nextScopePath;
+ QString m_defaultPropertyName;
+ QMultiMap<QString, PropertyDefinition> m_propertyDefs;
+ QMultiMap<QString, Binding> m_bindings;
+ QMultiMap<QString, MethodInfo> m_methods;
+ QList<QmlObject> m_children;
+ QList<QmlObject> m_annotations;
+ QQmlJSScope::ConstPtr m_scope;
+ ScriptElementVariant m_nameIdentifiers;
+};
+
+class Export
+{
+ Q_DECLARE_TR_FUNCTIONS(Export)
+public:
+ constexpr static DomType kindValue = DomType::Export;
+ static Export fromString(
+ const Path &source, QStringView exp, const Path &typePath, const ErrorHandler &h);
+ bool iterateDirectSubpaths(const DomItem &self, DirectVisitor visitor) const
+ {
+ bool cont = true;
+ cont = cont && self.dvValueField(visitor, Fields::uri, uri);
+ cont = cont && self.dvValueField(visitor, Fields::typeName, typeName);
+ cont = cont && self.dvWrapField(visitor, Fields::version, version);
+ if (typePath)
+ cont = cont && self.dvReferenceField(visitor, Fields::type, typePath);
+ cont = cont && self.dvValueField(visitor, Fields::isInternal, isInternal);
+ cont = cont && self.dvValueField(visitor, Fields::isSingleton, isSingleton);
+ if (exportSourcePath)
+ cont = cont && self.dvReferenceField(visitor, Fields::exportSource, exportSourcePath);
+ return cont;
+ }
+
+ Path exportSourcePath;
+ QString uri;
+ QString typeName;
+ Version version;
+ Path typePath;
+ bool isInternal = false;
+ bool isSingleton = false;
+};
+
+class QMLDOM_EXPORT Component : public CommentableDomElement
+{
+public:
+ Component(const QString &name);
+ Component(const Path &pathFromOwner = Path());
+ Component(const Component &o) = default;
+ Component &operator=(const Component &) = default;
+
+ bool iterateDirectSubpaths(const DomItem &, DirectVisitor) const override;
+ void updatePathFromOwner(const Path &newPath) override;
+ DomItem field(const DomItem &self, QStringView name) const override;
+
+ QString name() const { return m_name; }
+ const QMultiMap<QString, EnumDecl> &enumerations() const & { return m_enumerations; }
+ const QList<QmlObject> &objects() const & { return m_objects; }
+ bool isSingleton() const { return m_isSingleton; }
+ bool isCreatable() const { return m_isCreatable; }
+ bool isComposite() const { return m_isComposite; }
+ QString attachedTypeName() const { return m_attachedTypeName; }
+ Path attachedTypePath(const DomItem &) const { return m_attachedTypePath; }
+
+ void setName(const QString &name) { m_name = name; }
+ void setEnumerations(QMultiMap<QString, EnumDecl> enumerations)
+ {
+ m_enumerations = enumerations;
+ }
+ Path addEnumeration(const EnumDecl &enumeration, AddOption option = AddOption::Overwrite,
+ EnumDecl **ePtr = nullptr)
+ {
+ return insertUpdatableElementInMultiMap(pathFromOwner().field(Fields::enumerations),
+ m_enumerations, enumeration.name(), enumeration,
+ option, ePtr);
+ }
+ void setObjects(const QList<QmlObject> &objects) { m_objects = objects; }
+ Path addObject(const QmlObject &object, QmlObject **oPtr = nullptr);
+ void setIsSingleton(bool isSingleton) { m_isSingleton = isSingleton; }
+ void setIsCreatable(bool isCreatable) { m_isCreatable = isCreatable; }
+ void setIsComposite(bool isComposite) { m_isComposite = isComposite; }
+ void setAttachedTypeName(const QString &name) { m_attachedTypeName = name; }
+ void setAttachedTypePath(const Path &p) { m_attachedTypePath = p; }
+
+private:
+ friend class QQmlDomAstCreator;
+ QString m_name;
+ QMultiMap<QString, EnumDecl> m_enumerations;
+ QList<QmlObject> m_objects;
+ bool m_isSingleton = false;
+ bool m_isCreatable = true;
+ bool m_isComposite = true;
+ QString m_attachedTypeName;
+ Path m_attachedTypePath;
+};
+
+class QMLDOM_EXPORT JsResource final : public Component
+{
+public:
+ constexpr static DomType kindValue = DomType::JsResource;
+ DomType kind() const override { return kindValue; }
+
+ JsResource(const Path &pathFromOwner = Path()) : Component(pathFromOwner) { }
+ bool iterateDirectSubpaths(const DomItem &, DirectVisitor) const override
+ { // to do: complete
+ return true;
+ }
+ // globalSymbols defined/exported, required/used
+};
+
+class QMLDOM_EXPORT QmltypesComponent final : public Component
+{
+public:
+ constexpr static DomType kindValue = DomType::QmltypesComponent;
+ DomType kind() const override { return kindValue; }
+
+ QmltypesComponent(const Path &pathFromOwner = Path()) : Component(pathFromOwner) { }
+ bool iterateDirectSubpaths(const DomItem &, DirectVisitor) const override;
+ const QList<Export> &exports() const & { return m_exports; }
+ QString fileName() const { return m_fileName; }
+ void setExports(QList<Export> exports) { m_exports = exports; }
+ void addExport(const Export &exportedEntry) { m_exports.append(exportedEntry); }
+ void setFileName(const QString &fileName) { m_fileName = fileName; }
+ const QList<int> &metaRevisions() const & { return m_metaRevisions; }
+ void setMetaRevisions(QList<int> metaRevisions) { m_metaRevisions = metaRevisions; }
+ void setInterfaceNames(const QStringList& interfaces) { m_interfaceNames = interfaces; }
+ const QStringList &interfaceNames() const & { return m_interfaceNames; }
+ QString extensionTypeName() const { return m_extensionTypeName; }
+ void setExtensionTypeName(const QString &name) { m_extensionTypeName = name; }
+ QString valueTypeName() const { return m_valueTypeName; }
+ void setValueTypeName(const QString &name) { m_valueTypeName = name; }
+ bool hasCustomParser() const { return m_hasCustomParser; }
+ void setHasCustomParser(bool v) { m_hasCustomParser = v; }
+ bool extensionIsJavaScript() const { return m_extensionIsJavaScript; }
+ void setExtensionIsJavaScript(bool v) { m_extensionIsJavaScript = v; }
+ bool extensionIsNamespace() const { return m_extensionIsNamespace; }
+ void setExtensionIsNamespace(bool v) { m_extensionIsNamespace = v; }
+ QQmlJSScope::AccessSemantics accessSemantics() const { return m_accessSemantics; }
+ void setAccessSemantics(QQmlJSScope::AccessSemantics v) { m_accessSemantics = v; }
+
+ void setSemanticScope(const QQmlJSScope::ConstPtr &scope) { m_semanticScope = scope; }
+ QQmlJSScope::ConstPtr semanticScope() const { return m_semanticScope; }
+
+private:
+ QList<Export> m_exports;
+ QList<int> m_metaRevisions;
+ QString m_fileName; // remove?
+ QStringList m_interfaceNames;
+ bool m_hasCustomParser = false;
+ bool m_extensionIsJavaScript = false;
+ bool m_extensionIsNamespace = false;
+ QString m_valueTypeName;
+ QString m_extensionTypeName;
+ QQmlJSScope::AccessSemantics m_accessSemantics = QQmlJSScope::AccessSemantics::None;
+ QQmlJSScope::ConstPtr m_semanticScope;
+};
+
+class QMLDOM_EXPORT QmlComponent final : public Component
+{
+public:
+ constexpr static DomType kindValue = DomType::QmlComponent;
+ DomType kind() const override { return kindValue; }
+
+ QmlComponent(const QString &name = QString()) : Component(name)
+ {
+ setIsComposite(true);
+ setIsCreatable(true);
+ }
+
+ bool iterateDirectSubpaths(const DomItem &self, DirectVisitor) const override;
+
+ const QMultiMap<QString, Id> &ids() const & { return m_ids; }
+ Path nextComponentPath() const { return m_nextComponentPath; }
+ void setIds(QMultiMap<QString, Id> ids) { m_ids = ids; }
+ void setNextComponentPath(const Path &p) { m_nextComponentPath = p; }
+ void updatePathFromOwner(const Path &newPath) override;
+ Path addId(const Id &id, AddOption option = AddOption::Overwrite, Id **idPtr = nullptr)
+ {
+ // warning does nor remove old idStr when overwriting...
+ return insertUpdatableElementInMultiMap(pathFromOwner().field(Fields::ids), m_ids, id.name,
+ id, option, idPtr);
+ }
+ void writeOut(const DomItem &self, OutWriter &) const override;
+ QList<QString> subComponentsNames(const DomItem &self) const;
+ QList<DomItem> subComponents(const DomItem &self) const;
+
+ void setSemanticScope(const QQmlJSScope::ConstPtr &scope) { m_semanticScope = scope; }
+ QQmlJSScope::ConstPtr semanticScope() const { return m_semanticScope; }
+ ScriptElementVariant nameIdentifiers() const { return m_nameIdentifiers; }
+ void setNameIdentifiers(const ScriptElementVariant &name) { m_nameIdentifiers = name; }
+
+private:
+ friend class QQmlDomAstCreator;
+ Path m_nextComponentPath;
+ QMultiMap<QString, Id> m_ids;
+ QQmlJSScope::ConstPtr m_semanticScope;
+ // m_nameIdentifiers contains the name of the component as FieldMemberExpression, and therefore
+ // only exists in inline components!
+ ScriptElementVariant m_nameIdentifiers;
+};
+
+class QMLDOM_EXPORT GlobalComponent final : public Component
+{
+public:
+ constexpr static DomType kindValue = DomType::GlobalComponent;
+ DomType kind() const override { return kindValue; }
+
+ GlobalComponent(const Path &pathFromOwner = Path()) : Component(pathFromOwner) { }
+};
+
+static ErrorGroups importErrors = { { DomItem::domErrorGroup, NewErrorGroup("importError") } };
+
+class QMLDOM_EXPORT ImportScope
+{
+ Q_DECLARE_TR_FUNCTIONS(ImportScope)
+public:
+ constexpr static DomType kindValue = DomType::ImportScope;
+
+ ImportScope() = default;
+ ~ImportScope() = default;
+
+ const QList<Path> &importSourcePaths() const & { return m_importSourcePaths; }
+
+ const QMap<QString, ImportScope> &subImports() const & { return m_subImports; }
+
+ QList<Path> allSources(const DomItem &self) const;
+
+ QSet<QString> importedNames(const DomItem &self) const
+ {
+ QSet<QString> res;
+ const auto sources = allSources(self);
+ for (const Path &p : sources) {
+ QSet<QString> ks = self.path(p.field(Fields::exports), self.errorHandler()).keys();
+ res += ks;
+ }
+ return res;
+ }
+
+ QList<DomItem> importedItemsWithName(const DomItem &self, const QString &name) const
+ {
+ QList<DomItem> res;
+ const auto sources = allSources(self);
+ for (const Path &p : sources) {
+ DomItem source = self.path(p.field(Fields::exports), self.errorHandler());
+ DomItem els = source.key(name);
+ int nEls = els.indexes();
+ for (int i = 0; i < nEls; ++i)
+ res.append(els.index(i));
+ if (nEls == 0 && els) {
+ self.addError(importErrors.warning(
+ tr("Looking up '%1' expected a list of exports, not %2")
+ .arg(name, els.toString())));
+ }
+ }
+ return res;
+ }
+
+ QList<Export> importedExportsWithName(const DomItem &self, const QString &name) const
+ {
+ QList<Export> res;
+ for (const DomItem &i : importedItemsWithName(self, name))
+ if (const Export *e = i.as<Export>())
+ res.append(*e);
+ else
+ self.addError(importErrors.warning(
+ tr("Expected Export looking up '%1', not %2").arg(name, i.toString())));
+ return res;
+ }
+
+ bool iterateDirectSubpaths(const DomItem &self, DirectVisitor visitor) const;
+
+ void addImport(QStringList p, const Path &targetExports)
+ {
+ if (!p.isEmpty()) {
+ const QString current = p.takeFirst();
+ m_subImports[current].addImport(std::move(p), targetExports);
+ } else if (!m_importSourcePaths.contains(targetExports)) {
+ m_importSourcePaths.append(targetExports);
+ }
+ }
+
+private:
+ QList<Path> m_importSourcePaths;
+ QMap<QString, ImportScope> m_subImports;
+};
+
+class BindingValue
+{
+public:
+ BindingValue();
+ BindingValue(const QmlObject &o);
+ BindingValue(const std::shared_ptr<ScriptExpression> &o);
+ BindingValue(const QList<QmlObject> &l);
+ ~BindingValue();
+ BindingValue(const BindingValue &o);
+ BindingValue &operator=(const BindingValue &o);
+
+ DomItem value(const DomItem &binding) const;
+ void updatePathFromOwner(const Path &newPath);
+
+private:
+ friend class Binding;
+ void clearValue();
+
+ BindingValueKind kind;
+ union {
+ int dummy;
+ QmlObject object;
+ std::shared_ptr<ScriptExpression> scriptExpression;
+ QList<QmlObject> array;
+ };
+};
+
+} // end namespace Dom
+} // end namespace QQmlJS
+QT_END_NAMESPACE
+#endif // QQMLDOMELEMENTS_P_H
diff --git a/src/qmldom/qqmldomerrormessage.cpp b/src/qmldom/qqmldomerrormessage.cpp
index e0e9448cc4..b48702822f 100644
--- a/src/qmldom/qqmldomerrormessage.cpp
+++ b/src/qmldom/qqmldomerrormessage.cpp
@@ -1,43 +1,8 @@
-/****************************************************************************
-**
-** 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 "qqmldomerrormessage_p.h"
+// Copyright (C) 2020 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
#include "qqmldomitem_p.h"
#include "qqmldomstringdumper_p.h"
+#include "qqmldomattachedinfo_p.h"
#include <QtCore/QCborMap>
#include <QtCore/QMutex>
@@ -48,6 +13,8 @@ QT_BEGIN_NAMESPACE
namespace QQmlJS {
namespace Dom {
+Q_LOGGING_CATEGORY(domLog, "qt.qmldom", QtWarningMsg);
+
enum {
FatalMsgMaxLen=511
};
@@ -70,14 +37,14 @@ Every group has a unique string identifying it (the \l{groupId}), and it should
be translated to get the local name. The best way to acheive this is to create new groups using
the NewErrorGroup macro.
*/
-void ErrorGroup::dump(Sink sink) const
+void ErrorGroup::dump(const Sink &sink) const
{
sink(u"[");
sink(groupName());
sink(u"]");
}
-void ErrorGroup::dumpId(Sink sink) const
+void ErrorGroup::dumpId(const Sink &sink) const
{
sink(u"[");
sink(QString(groupId()));
@@ -103,22 +70,22 @@ The simplest way to create new ErrorMessages is to have an ErrorGroups instance,
and use it to create new ErrorMessages using its debug, warning, error,... methods
*/
-void ErrorGroups::dump(Sink sink) const
+void ErrorGroups::dump(const Sink &sink) const
{
- for (int i = 0; i < groups.length(); ++i)
+ for (int i = 0; i < groups.size(); ++i)
groups.at(i).dump(sink);
}
-void ErrorGroups::dumpId(Sink sink) const
+void ErrorGroups::dumpId(const Sink &sink) const
{
- for (int i = 0; i < groups.length(); ++i)
+ for (int i = 0; i < groups.size(); ++i)
groups.at(i).dumpId(sink);
}
QCborArray ErrorGroups::toCbor() const
{
QCborArray res;
- for (int i = 0; i < groups.length(); ++i)
+ for (int i = 0; i < groups.size(); ++i)
res.append(QCborValue(groups.at(i).groupId()));
return res;
}
@@ -171,31 +138,36 @@ errorHandler(ErrorMessage::load(QLatin1String("my.company.error1")));
The \l{withItem} method can be used to set the path file and location if not aready set.
*/
-ErrorMessage ErrorGroups::errorMessage(Dumper msg, ErrorLevel level, Path element, QString canonicalFilePath, SourceLocation location) const
+ErrorMessage ErrorGroups::errorMessage(
+ const Dumper &msg, ErrorLevel level, const Path &element, const QString &canonicalFilePath,
+ SourceLocation location) const
{
if (level == ErrorLevel::Fatal)
fatal(msg, element, canonicalFilePath, location);
return ErrorMessage(dumperToString(msg), *this, level, element, canonicalFilePath, location);
}
-ErrorMessage ErrorGroups::errorMessage(const DiagnosticMessage &msg, Path element, QString canonicalFilePath) const
+ErrorMessage ErrorGroups::errorMessage(const DiagnosticMessage &msg, const Path &element, const QString &canonicalFilePath) const
{
ErrorMessage res(*this, msg, element, canonicalFilePath);
- if (!res.location.isValid() && (res.location.startLine != 0 || res.location.startColumn != 0)) {
+ if (res.location == SourceLocation()
+ && (res.location.startLine != 0 || res.location.startColumn != 0)) {
res.location.offset = -1;
res.location.length = 1;
}
return res;
}
-void ErrorGroups::fatal(Dumper msg, Path element, QStringView canonicalFilePath, SourceLocation location) const
+void ErrorGroups::fatal(
+ const Dumper &msg, const Path &element, QStringView canonicalFilePath,
+ SourceLocation location) const
{
enum { FatalMsgMaxLen = 1023 };
char buf[FatalMsgMaxLen+1];
int ibuf = 0;
auto sink = [&ibuf, &buf](QStringView s) {
int is = 0;
- while (ibuf < FatalMsgMaxLen && is < s.length()) {
+ while (ibuf < FatalMsgMaxLen && is < s.size()) {
QChar c = s.at(is);
if (c == QChar::fromLatin1('\n') || c == QChar::fromLatin1('\r') || (c >= QChar::fromLatin1(' ') && c <= QChar::fromLatin1('~')))
buf[ibuf++] = c.toLatin1();
@@ -224,72 +196,42 @@ void ErrorGroups::fatal(Dumper msg, Path element, QStringView canonicalFilePath,
qFatal("%s", buf);
}
-ErrorMessage ErrorGroups::debug(QString message) const
+ErrorMessage ErrorGroups::debug(const QString &message) const
{
return ErrorMessage(message, *this, ErrorLevel::Debug);
}
-ErrorMessage ErrorGroups::debug(Dumper message) const
+ErrorMessage ErrorGroups::debug(const Dumper &message) const
{
return ErrorMessage(dumperToString(message), *this, ErrorLevel::Debug);
}
-ErrorMessage ErrorGroups::info(QString message) const
+ErrorMessage ErrorGroups::info(const QString &message) const
{
return ErrorMessage(message, *this, ErrorLevel::Info);
}
-ErrorMessage ErrorGroups::info(Dumper message) const
+ErrorMessage ErrorGroups::info(const Dumper &message) const
{
return ErrorMessage(dumperToString(message), *this, ErrorLevel::Info);
}
-ErrorMessage ErrorGroups::hint(QString message) const
-{
- return ErrorMessage(message, *this, ErrorLevel::Hint);
-}
-
-ErrorMessage ErrorGroups::hint(Dumper message) const
-{
- return ErrorMessage(dumperToString(message), *this, ErrorLevel::Hint);
-}
-
-ErrorMessage ErrorGroups::maybeWarning(QString message) const
-{
- return ErrorMessage(message, *this, ErrorLevel::MaybeWarning);
-}
-
-ErrorMessage ErrorGroups::maybeWarning(Dumper message) const
-{
- return ErrorMessage(dumperToString(message), *this, ErrorLevel::MaybeWarning);
-}
-
-ErrorMessage ErrorGroups::warning(QString message) const
+ErrorMessage ErrorGroups::warning(const QString &message) const
{
return ErrorMessage(message, *this, ErrorLevel::Warning);
}
-ErrorMessage ErrorGroups::warning(Dumper message) const
+ErrorMessage ErrorGroups::warning(const Dumper &message) const
{
return ErrorMessage(dumperToString(message), *this, ErrorLevel::Warning);
}
-ErrorMessage ErrorGroups::maybeError(QString message) const
-{
- return ErrorMessage(message, *this, ErrorLevel::MaybeError);
-}
-
-ErrorMessage ErrorGroups::maybeError(Dumper message) const
-{
- return ErrorMessage(dumperToString(message), *this, ErrorLevel::MaybeError);
-}
-
-ErrorMessage ErrorGroups::error(QString message) const
+ErrorMessage ErrorGroups::error(const QString &message) const
{
return ErrorMessage(message, *this, ErrorLevel::Error);
}
-ErrorMessage ErrorGroups::error(Dumper message) const
+ErrorMessage ErrorGroups::error(const Dumper &message) const
{
return ErrorMessage(dumperToString(message), *this, ErrorLevel::Error);
}
@@ -298,11 +240,11 @@ int ErrorGroups::cmp(const ErrorGroups &o1, const ErrorGroups &o2)
{
auto &g1 = o1.groups;
auto &g2 = o2.groups;
- if (g1.length() < g2.length())
+ if (g1.size() < g2.size())
return -1;
- if (g1.length() < g2.length())
+ if (g1.size() < g2.size())
return 1;
- for (int i = 0; i < g1.length(); ++i) {
+ for (int i = 0; i < g1.size(); ++i) {
int c = std::strcmp(g1.at(i).groupId().data(), g2.at(i).groupId().data());
if (c != 0)
return c;
@@ -310,17 +252,31 @@ int ErrorGroups::cmp(const ErrorGroups &o1, const ErrorGroups &o2)
return 0;
}
-ErrorMessage::ErrorMessage(QString msg, ErrorGroups errorGroups, Level level, Path element, QString canonicalFilePath, SourceLocation location, QLatin1String errorId):
- errorId(errorId), message(msg), errorGroups(errorGroups), level(level), path(element), file(canonicalFilePath), location(location)
+ErrorMessage::ErrorMessage(
+ const QString &msg, const ErrorGroups &errorGroups, Level level, const Path &element,
+ const QString &canonicalFilePath, SourceLocation location, QLatin1String errorId)
+ : errorId(errorId)
+ , message(msg)
+ , errorGroups(errorGroups)
+ , level(level)
+ , path(element)
+ , file(canonicalFilePath)
+ , location(location)
{
if (level == Level::Fatal) // we should not end up here, it should have been handled at a higher level already
errorGroups.fatal(msg, element, canonicalFilePath, location);
}
-ErrorMessage::ErrorMessage(ErrorGroups errorGroups, const DiagnosticMessage &msg, Path element,
- QString canonicalFilePath, QLatin1String errorId):
- errorId(errorId), message(msg.message), errorGroups(errorGroups),
- level(errorLevelFromQtMsgType(msg.type)), path(element), file(canonicalFilePath), location(msg.loc)
+ErrorMessage::ErrorMessage(
+ const ErrorGroups &errorGroups, const DiagnosticMessage &msg, const Path &element,
+ const QString &canonicalFilePath, QLatin1String errorId)
+ : errorId(errorId)
+ , message(msg.message)
+ , errorGroups(errorGroups)
+ , level(errorLevelFromQtMsgType(msg.type))
+ , path(element)
+ , file(canonicalFilePath)
+ , location(msg.loc)
{
if (level == Level::Fatal) // we should not end up here, it should have been handled at a higher level already
errorGroups.fatal(msg.message, element, canonicalFilePath, location);
@@ -349,6 +305,10 @@ struct StorableMsg {
msg(e)
{}
+ StorableMsg(ErrorMessage &&e):
+ msg(std::move(e))
+ {}
+
ErrorMessage msg;
};
@@ -358,12 +318,12 @@ static QHash<QLatin1String, StorableMsg> &registry()
return r;
}
-QLatin1String ErrorMessage::msg(const char *errorId, ErrorMessage err)
+QLatin1String ErrorMessage::msg(const char *errorId, ErrorMessage &&err)
{
- return msg(QLatin1String(errorId), err);
+ return msg(QLatin1String(errorId), std::move(err));
}
-QLatin1String ErrorMessage::msg(QLatin1String errorId, ErrorMessage err)
+QLatin1String ErrorMessage::msg(QLatin1String errorId, ErrorMessage &&err)
{
bool doubleRegister = false;
ErrorMessage old = myErrors().debug(u"dummy");
@@ -374,14 +334,14 @@ QLatin1String ErrorMessage::msg(QLatin1String errorId, ErrorMessage err)
old = r[err.errorId].msg;
doubleRegister = true;
}
- r[errorId] = StorableMsg{err.withErrorId(errorId)};
+ r[errorId] = StorableMsg{std::move(err.withErrorId(errorId))};
}
if (doubleRegister)
defaultErrorHandler(myErrors().warning(tr("Double registration of error %1: (%2) vs (%3)").arg(errorId, err.withErrorId(errorId).toString(), old.toString())));
return errorId;
}
-void ErrorMessage::visitRegisteredMessages(std::function<bool (ErrorMessage)> visitor)
+void ErrorMessage::visitRegisteredMessages(function_ref<bool(const ErrorMessage &)> visitor)
{
QHash<QLatin1String, StorableMsg> r;
{
@@ -398,7 +358,7 @@ void ErrorMessage::visitRegisteredMessages(std::function<bool (ErrorMessage)> vi
ErrorMessage ErrorMessage::load(QLatin1String errorId)
{
- ErrorMessage res = myErrors().error([errorId](Sink s){
+ ErrorMessage res = myErrors().error([errorId](const Sink &s){
s(u"Unregistered error ");
s(QString(errorId)); });
{
@@ -425,7 +385,7 @@ ErrorMessage &ErrorMessage::withPath(const Path &path)
return *this;
}
-ErrorMessage &ErrorMessage::withFile(QString f)
+ErrorMessage &ErrorMessage::withFile(const QString &f)
{
file=f;
return *this;
@@ -443,14 +403,17 @@ ErrorMessage &ErrorMessage::withLocation(SourceLocation loc)
return *this;
}
-ErrorMessage &ErrorMessage::withItem(DomItem el)
+ErrorMessage &ErrorMessage::withItem(const DomItem &el)
{
if (path.length() == 0)
path = el.canonicalPath();
if (file.isEmpty())
file = el.canonicalFilePath();
- if (!location.isValid())
- location = el.location();
+ if (location == SourceLocation()) {
+ if (const FileLocations::Tree tree = FileLocations::treeOf(el)) {
+ location = FileLocations::region(tree, MainRegion);
+ }
+ }
return *this;
}
@@ -463,7 +426,7 @@ ErrorMessage ErrorMessage::handle(const ErrorHandler &errorHandler)
return *this;
}
-void ErrorMessage::dump(Sink sink) const
+void ErrorMessage::dump(const Sink &sink) const
{
if (!file.isEmpty()) {
sink(file);
@@ -486,13 +449,16 @@ void ErrorMessage::dump(Sink sink) const
sink(message);
if (path.length()>0) {
sink(u" for ");
- path.dump(sink);
+ if (!file.isEmpty() && path.length() > 3 && path.headKind() == Path::Kind::Root)
+ path.mid(3).dump(sink);
+ else
+ path.dump(sink);
}
}
QString ErrorMessage::toString() const
{
- return dumperToString([this](Sink sink){ this->dump(sink); });
+ return dumperToString([this](const Sink &sink){ this->dump(sink); });
}
QCborMap ErrorMessage::toCbor() const
@@ -519,7 +485,7 @@ QCborMap ErrorMessage::toCbor() const
*/
void errorToQDebug(const ErrorMessage &error)
{
- dumperToQDebug([&error](Sink s){ error.dump(s); }, error.level);
+ dumperToQDebug([&error](const Sink &s){ error.dump(s); }, error.level);
}
/*!
@@ -530,7 +496,7 @@ void silentError(const ErrorMessage &)
{
}
-void errorHandlerHandler(const ErrorMessage &msg, ErrorHandler *h = nullptr)
+void errorHandlerHandler(const ErrorMessage &msg, const ErrorHandler *h = nullptr)
{
static ErrorHandler handler = &errorToQDebug;
if (h) {
@@ -553,7 +519,7 @@ void defaultErrorHandler(const ErrorMessage &error)
* \internal
* \brief Sets the default error handler
*/
-void setDefaultErrorHandler(ErrorHandler h)
+void setDefaultErrorHandler(const ErrorHandler &h)
{
errorHandlerHandler(ErrorMessage(QString(), ErrorGroups({})), &h);
}
@@ -580,3 +546,5 @@ ErrorLevel errorLevelFromQtMsgType(QtMsgType msgType)
} // end namespace QQmlJS
QT_END_NAMESPACE
+
+#include "moc_qqmldomerrormessage_p.cpp"
diff --git a/src/qmldom/qqmldomerrormessage_p.h b/src/qmldom/qqmldomerrormessage_p.h
index f17c009835..20e2d817e0 100644
--- a/src/qmldom/qqmldomerrormessage_p.h
+++ b/src/qmldom/qqmldomerrormessage_p.h
@@ -1,40 +1,6 @@
-/****************************************************************************
-**
-** 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$
-**/
+// Copyright (C) 2020 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+
#ifndef ERRORMESSAGE_H
#define ERRORMESSAGE_H
@@ -58,6 +24,7 @@
#include <QtCore/QString>
#include <QtCore/QCborArray>
#include <QtCore/QCborMap>
+#include <QtCore/QLoggingCategory>
#include <QtQml/private/qqmljsdiagnosticmessage_p.h>
QT_BEGIN_NAMESPACE
@@ -65,6 +32,8 @@ QT_BEGIN_NAMESPACE
namespace QQmlJS {
namespace Dom {
+Q_DECLARE_LOGGING_CATEGORY(domLog);
+
QMLDOM_EXPORT ErrorLevel errorLevelFromQtMsgType(QtMsgType msgType);
class ErrorGroups;
@@ -82,8 +51,8 @@ public:
{}
- void dump(Sink sink) const;
- void dumpId(Sink sink) const;
+ void dump(const Sink &sink) const;
+ void dumpId(const Sink &sink) const;
QLatin1String groupId() const;
QString groupName() const;
@@ -94,29 +63,28 @@ public:
class QMLDOM_EXPORT ErrorGroups{
Q_GADGET
public:
- void dump(Sink sink) const;
- void dumpId(Sink sink) const;
+ void dump(const Sink &sink) const;
+ void dumpId(const Sink &sink) const;
QCborArray toCbor() const;
- [[nodiscard]] ErrorMessage errorMessage(Dumper msg, ErrorLevel level, Path element = Path(), QString canonicalFilePath = QString(), SourceLocation location = SourceLocation()) const;
- [[nodiscard]] ErrorMessage errorMessage(const DiagnosticMessage &msg, Path element = Path(), QString canonicalFilePath = QString()) const;
-
- void fatal(Dumper msg, Path element = Path(), QStringView canonicalFilePath = u"", SourceLocation location = SourceLocation()) const;
-
- [[nodiscard]] ErrorMessage debug(QString message) const;
- [[nodiscard]] ErrorMessage debug(Dumper message) const;
- [[nodiscard]] ErrorMessage info(QString message) const;
- [[nodiscard]] ErrorMessage info(Dumper message) const;
- [[nodiscard]] ErrorMessage hint(QString message) const;
- [[nodiscard]] ErrorMessage hint(Dumper message) const;
- [[nodiscard]] ErrorMessage maybeWarning(QString message) const;
- [[nodiscard]] ErrorMessage maybeWarning(Dumper message) const;
- [[nodiscard]] ErrorMessage warning(QString message) const;
- [[nodiscard]] ErrorMessage warning(Dumper message) const;
- [[nodiscard]] ErrorMessage maybeError(QString message) const;
- [[nodiscard]] ErrorMessage maybeError(Dumper message) const;
- [[nodiscard]] ErrorMessage error(QString message) const;
- [[nodiscard]] ErrorMessage error(Dumper message) const;
+ [[nodiscard]] ErrorMessage errorMessage(
+ const Dumper &msg, ErrorLevel level, const Path &element = Path(),
+ const QString &canonicalFilePath = QString(), SourceLocation location = SourceLocation()) const;
+ [[nodiscard]] ErrorMessage errorMessage(
+ const DiagnosticMessage &msg, const Path &element = Path(),
+ const QString &canonicalFilePath = QString()) const;
+
+ void fatal(const Dumper &msg, const Path &element = Path(), QStringView canonicalFilePath = u"",
+ SourceLocation location = SourceLocation()) const;
+
+ [[nodiscard]] ErrorMessage debug(const QString &message) const;
+ [[nodiscard]] ErrorMessage debug(const Dumper &message) const;
+ [[nodiscard]] ErrorMessage info(const QString &message) const;
+ [[nodiscard]] ErrorMessage info(const Dumper &message) const;
+ [[nodiscard]] ErrorMessage warning(const QString &message) const;
+ [[nodiscard]] ErrorMessage warning(const Dumper &message) const;
+ [[nodiscard]] ErrorMessage error(const QString &message) const;
+ [[nodiscard]] ErrorMessage error(const Dumper &message) const;
static int cmp(const ErrorGroups &g1, const ErrorGroups &g2);
@@ -136,9 +104,9 @@ class QMLDOM_EXPORT ErrorMessage { // reuse Some of the other DiagnosticMessages
public:
using Level = ErrorLevel;
// error registry (usage is optional)
- static QLatin1String msg(const char *errorId, ErrorMessage err);
- static QLatin1String msg(QLatin1String errorId, ErrorMessage err);
- static void visitRegisteredMessages(std::function<bool(ErrorMessage)> visitor);
+ static QLatin1String msg(const char *errorId, ErrorMessage &&err);
+ static QLatin1String msg(QLatin1String errorId, ErrorMessage &&err);
+ static void visitRegisteredMessages(function_ref<bool (const ErrorMessage &)> visitor);
[[nodiscard]] static ErrorMessage load(QLatin1String errorId);
[[nodiscard]] static ErrorMessage load(const char *errorId);
template<typename... T>
@@ -148,21 +116,66 @@ public:
return res;
}
- ErrorMessage(QString message, ErrorGroups errorGroups, Level level = Level::Warning, Path path = Path(), QString file = QString(), SourceLocation location = SourceLocation(), QLatin1String errorId = QLatin1String(""));
- ErrorMessage(ErrorGroups errorGroups, const DiagnosticMessage &msg, Path path = Path(), QString file = QString(), QLatin1String errorId = QLatin1String(""));
+ ErrorMessage(
+ const QString &message, const ErrorGroups &errorGroups, Level level = Level::Warning,
+ const Path &path = Path(), const QString &file = QString(),
+ SourceLocation location = SourceLocation(), QLatin1String errorId = QLatin1String(""));
+ ErrorMessage(
+ const ErrorGroups &errorGroups, const DiagnosticMessage &msg, const Path &path = Path(),
+ const QString &file = QString(), QLatin1String errorId = QLatin1String(""));
[[nodiscard]] ErrorMessage &withErrorId(QLatin1String errorId);
[[nodiscard]] ErrorMessage &withPath(const Path &);
- [[nodiscard]] ErrorMessage &withFile(QString);
+ [[nodiscard]] ErrorMessage &withFile(const QString &);
[[nodiscard]] ErrorMessage &withFile(QStringView);
[[nodiscard]] ErrorMessage &withLocation(SourceLocation);
- [[nodiscard]] ErrorMessage &withItem(DomItem);
+ [[nodiscard]] ErrorMessage &withItem(const DomItem &);
ErrorMessage handle(const ErrorHandler &errorHandler=nullptr);
- void dump(Sink s) const;
+ void dump(const Sink &s) const;
QString toString() const;
QCborMap toCbor() const;
+ friend int compare(const ErrorMessage &msg1, const ErrorMessage &msg2)
+ {
+ int c;
+ c = msg1.location.offset - msg2.location.offset;
+ if (c != 0)
+ return c;
+ c = msg1.location.startLine - msg2.location.startLine;
+ if (c != 0)
+ return c;
+ c = msg1.errorId.compare(msg2.errorId);
+ if (c != 0)
+ return c;
+ if (!msg1.errorId.isEmpty())
+ return 0;
+ c = msg1.message.compare(msg2.message);
+ if (c != 0)
+ return c;
+ c = msg1.file.compare(msg2.file);
+ if (c != 0)
+ return c;
+ c = Path::cmp(msg1.path, msg2.path);
+ if (c != 0)
+ return c;
+ c = int(msg1.level) - int(msg2.level);
+ if (c != 0)
+ return c;
+ c = int(msg1.errorGroups.groups.size() - msg2.errorGroups.groups.size());
+ if (c != 0)
+ return c;
+ for (qsizetype i = 0; i < msg1.errorGroups.groups.size(); ++i) {
+ c = msg1.errorGroups.groups[i].groupId().compare(msg2.errorGroups.groups[i].groupId());
+ if (c != 0)
+ return c;
+ }
+ c = msg1.location.length - msg2.location.length;
+ if (c != 0)
+ return c;
+ c = msg1.location.startColumn - msg2.location.startColumn;
+ return c;
+ }
QLatin1String errorId;
QString message;
@@ -174,20 +187,33 @@ public:
};
inline bool operator !=(const ErrorMessage &e1, const ErrorMessage &e2) {
- return e1.errorId != e2.errorId || e1.message != e2.message || e1.errorGroups != e2.errorGroups
- || e1.level != e2.level || e1.path != e2.path || e1.file != e2.file
- || e1.location.startLine != e2.location.startLine || e1.location.offset != e2.location.offset
- || e1.location.startColumn != e2.location.startColumn || e1.location.length != e2.location.length;
+ return compare(e1, e2) != 0;
}
inline bool operator ==(const ErrorMessage &e1, const ErrorMessage &e2) {
- return !(e1 != e2);
+ return compare(e1, e2) == 0;
+}
+inline bool operator<(const ErrorMessage &e1, const ErrorMessage &e2)
+{
+ return compare(e1, e2) < 0;
+}
+inline bool operator<=(const ErrorMessage &e1, const ErrorMessage &e2)
+{
+ return compare(e1, e2) <= 0;
+}
+inline bool operator>(const ErrorMessage &e1, const ErrorMessage &e2)
+{
+ return compare(e1, e2) > 0;
+}
+inline bool operator>=(const ErrorMessage &e1, const ErrorMessage &e2)
+{
+ return compare(e1, e2) >= 0;
}
QMLDOM_EXPORT void silentError(const ErrorMessage &);
QMLDOM_EXPORT void errorToQDebug(const ErrorMessage &);
QMLDOM_EXPORT void defaultErrorHandler(const ErrorMessage &);
-QMLDOM_EXPORT void setDefaultErrorHandler(ErrorHandler h);
+QMLDOM_EXPORT void setDefaultErrorHandler(const ErrorHandler &h);
} // end namespace Dom
} // end namespace QQmlJS
diff --git a/src/qmldom/qqmldomexternalitems.cpp b/src/qmldom/qqmldomexternalitems.cpp
index 2d71cbc170..6f48aa19e3 100644
--- a/src/qmldom/qqmldomexternalitems.cpp
+++ b/src/qmldom/qqmldomexternalitems.cpp
@@ -1,43 +1,12 @@
-/****************************************************************************
-**
-** 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 "qqmldomexternalitems_p.h"
+// Copyright (C) 2020 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
#include "qqmldomtop_p.h"
+#include "qqmldomoutwriter_p.h"
+#include "qqmldomcomments_p.h"
+#include "qqmldommock_p.h"
+#include "qqmldomelements_p.h"
+#include "qqmldom_utils_p.h"
#include <QtQml/private/qqmljslexer_p.h>
#include <QtQml/private/qqmljsparser_p.h>
@@ -47,21 +16,24 @@
#include <QtCore/QDir>
#include <QtCore/QScopeGuard>
#include <QtCore/QFileInfo>
+#include <QtCore/QRegularExpressionMatch>
#include <algorithm>
QT_BEGIN_NAMESPACE
+using namespace Qt::StringLiterals;
+
namespace QQmlJS {
namespace Dom {
-ExternalOwningItem::ExternalOwningItem(QString filePath, QDateTime lastDataUpdateAt, Path path, int derivedFrom):
- OwningItem(derivedFrom, lastDataUpdateAt), m_canonicalFilePath(filePath), m_path(path)
-{}
-
-ExternalOwningItem::ExternalOwningItem(const ExternalOwningItem &o):
- OwningItem(o), m_canonicalFilePath(o.m_canonicalFilePath),
- m_path(o.m_path), m_isValid(o.m_isValid)
+ExternalOwningItem::ExternalOwningItem(
+ const QString &filePath, const QDateTime &lastDataUpdateAt, const Path &path,
+ int derivedFrom, const QString &code)
+ : OwningItem(derivedFrom, lastDataUpdateAt),
+ m_canonicalFilePath(filePath),
+ m_code(code),
+ m_path(path)
{}
QString ExternalOwningItem::canonicalFilePath(const DomItem &) const
@@ -84,6 +56,573 @@ Path ExternalOwningItem::canonicalPath() const
return m_path;
}
+ErrorGroups QmldirFile::myParsingErrors()
+{
+ static ErrorGroups res = { { DomItem::domErrorGroup, NewErrorGroup("Qmldir"),
+ NewErrorGroup("Parsing") } };
+ return res;
+}
+
+std::shared_ptr<QmldirFile> QmldirFile::fromPathAndCode(const QString &path, const QString &code)
+{
+ QString canonicalFilePath = QFileInfo(path).canonicalFilePath();
+
+ QDateTime dataUpdate = QDateTime::currentDateTimeUtc();
+ auto res = std::make_shared<QmldirFile>(canonicalFilePath, code, dataUpdate);
+
+ if (canonicalFilePath.isEmpty() && !path.isEmpty())
+ res->addErrorLocal(
+ myParsingErrors().error(tr("QmldirFile started from invalid path '%1'").arg(path)));
+ res->parse();
+ return res;
+}
+
+void QmldirFile::parse()
+{
+ if (canonicalFilePath().isEmpty()) {
+ addErrorLocal(myParsingErrors().error(tr("canonicalFilePath is empty")));
+ setIsValid(false);
+ } else {
+ m_qmldir.parse(m_code);
+ setFromQmldir();
+ }
+}
+
+void QmldirFile::setFromQmldir()
+{
+ m_uri = QmlUri::fromUriString(m_qmldir.typeNamespace());
+ if (m_uri.isValid())
+ m_uri = QmlUri::fromDirectoryString(canonicalFilePath());
+ Path exportsPath = Path::Field(Fields::exports);
+ QDir baseDir = QFileInfo(canonicalFilePath()).dir();
+ int majorVersion = Version::Undefined;
+ bool ok;
+ int vNr = QFileInfo(baseDir.dirName()).suffix().toInt(&ok);
+ if (ok && vNr > 0) // accept 0?
+ majorVersion = vNr;
+ Path exportSource = canonicalPath();
+ for (auto const &el : m_qmldir.components()) {
+ QString exportFilePath = baseDir.filePath(el.fileName);
+ QString canonicalExportFilePath = QFileInfo(exportFilePath).canonicalFilePath();
+ if (canonicalExportFilePath.isEmpty()) // file does not exist (yet? assuming it might be
+ // created where we expect it)
+ canonicalExportFilePath = exportFilePath;
+ Export exp;
+ exp.exportSourcePath = exportSource;
+ exp.isSingleton = el.singleton;
+ exp.isInternal = el.internal;
+ exp.version =
+ Version((el.version.hasMajorVersion() ? el.version.majorVersion() : majorVersion),
+ el.version.hasMinorVersion() ? el.version.minorVersion() : 0);
+ exp.typeName = el.typeName;
+ exp.typePath = Paths::qmlFileObjectPath(canonicalExportFilePath);
+ exp.uri = uri().toString();
+ m_exports.insert(exp.typeName, exp);
+ if (exp.version.majorVersion > 0)
+ m_majorVersions.insert(exp.version.majorVersion);
+ }
+ for (auto const &el : m_qmldir.scripts()) {
+ QString exportFilePath = baseDir.filePath(el.fileName);
+ QString canonicalExportFilePath = QFileInfo(exportFilePath).canonicalFilePath();
+ if (canonicalExportFilePath.isEmpty()) // file does not exist (yet? assuming it might be
+ // created where we expect it)
+ canonicalExportFilePath = exportFilePath;
+ Export exp;
+ exp.exportSourcePath = exportSource;
+ exp.isSingleton = true;
+ exp.isInternal = false;
+ exp.version =
+ Version((el.version.hasMajorVersion() ? el.version.majorVersion() : majorVersion),
+ el.version.hasMinorVersion() ? el.version.minorVersion() : 0);
+ exp.typePath = Paths::jsFilePath(canonicalExportFilePath).field(Fields::rootComponent);
+ exp.uri = uri().toString();
+ exp.typeName = el.nameSpace;
+ m_exports.insert(exp.typeName, exp);
+ if (exp.version.majorVersion > 0)
+ m_majorVersions.insert(exp.version.majorVersion);
+ }
+ for (QQmlDirParser::Import const &imp : m_qmldir.imports()) {
+ QString uri = imp.module;
+ bool isAutoImport = imp.flags & QQmlDirParser::Import::Auto;
+ Version v;
+ if (isAutoImport)
+ v = Version(majorVersion, int(Version::Latest));
+ else {
+ v = Version((imp.version.hasMajorVersion() ? imp.version.majorVersion()
+ : int(Version::Latest)),
+ (imp.version.hasMinorVersion() ? imp.version.minorVersion()
+ : int(Version::Latest)));
+ }
+ m_imports.append(Import(QmlUri::fromUriString(uri), v));
+ m_autoExports.append(
+ ModuleAutoExport { Import(QmlUri::fromUriString(uri), v), isAutoImport });
+ }
+ for (QQmlDirParser::Import const &imp : m_qmldir.dependencies()) {
+ QString uri = imp.module;
+ if (imp.flags & QQmlDirParser::Import::Auto) {
+ qCDebug(QQmlJSDomImporting) << "QmldirFile::setFromQmlDir: ignoring initial version"
+ " 'auto' in depends command, using latest version"
+ " instead.";
+ }
+ Version v = Version(
+ (imp.version.hasMajorVersion() ? imp.version.majorVersion() : int(Version::Latest)),
+ (imp.version.hasMinorVersion() ? imp.version.minorVersion()
+ : int(Version::Latest)));
+ m_imports.append(Import(QmlUri::fromUriString(uri), v));
+ }
+ bool hasInvalidTypeinfo = false;
+ for (auto const &el : m_qmldir.typeInfos()) {
+ QString elStr = el;
+ QFileInfo elPath(elStr);
+ if (elPath.isRelative())
+ elPath = QFileInfo(baseDir.filePath(elStr));
+ QString typeInfoPath = elPath.canonicalFilePath();
+ if (typeInfoPath.isEmpty()) {
+ hasInvalidTypeinfo = true;
+ typeInfoPath = elPath.absoluteFilePath();
+ }
+ m_qmltypesFilePaths.append(Paths::qmltypesFilePath(typeInfoPath));
+ }
+ if (m_qmltypesFilePaths.isEmpty() || hasInvalidTypeinfo) {
+ // add all type info files in the directory...
+ for (QFileInfo const &entry :
+ baseDir.entryInfoList(QStringList({ QLatin1String("*.qmltypes") }),
+ QDir::Filter::Readable | QDir::Filter::Files)) {
+ Path p = Paths::qmltypesFilePath(entry.canonicalFilePath());
+ if (!m_qmltypesFilePaths.contains(p))
+ m_qmltypesFilePaths.append(p);
+ }
+ }
+ bool hasErrors = false;
+ for (auto const &el : m_qmldir.errors(uri().toString())) {
+ ErrorMessage msg = myParsingErrors().errorMessage(el);
+ if (msg.level == ErrorLevel::Error || msg.level == ErrorLevel::Fatal)
+ hasErrors = true;
+ addErrorLocal(std::move(msg));
+ }
+ setIsValid(!hasErrors); // consider it valid also with errors?
+ m_plugins = m_qmldir.plugins();
+}
+
+QList<ModuleAutoExport> QmldirFile::autoExports() const
+{
+ return m_autoExports;
+}
+
+void QmldirFile::setAutoExports(const QList<ModuleAutoExport> &autoExport)
+{
+ m_autoExports = autoExport;
+}
+
+void QmldirFile::ensureInModuleIndex(const DomItem &self, const QString &uri) const
+{
+ // ModuleIndex keeps the various sources of types from a given module uri import
+ // this method ensures that all major versions that are contained in this qmldir
+ // file actually have a ModuleIndex. This is required so that when importing the
+ // latest version the correct "lastest major version" is found, for example for
+ // qml only modules (qmltypes files also register their versions)
+ DomItem env = self.environment();
+ if (std::shared_ptr<DomEnvironment> envPtr = env.ownerAs<DomEnvironment>()) {
+ for (int majorV : m_majorVersions) {
+ auto mIndex = envPtr->moduleIndexWithUri(env, uri, majorV, EnvLookup::Normal,
+ Changeable::Writable);
+ }
+ }
+}
+
+QCborValue pluginData(const QQmlDirParser::Plugin &pl, const QStringList &cNames)
+{
+ QCborArray names;
+ for (const QString &n : cNames)
+ names.append(n);
+ return QCborMap({ { QCborValue(QStringView(Fields::name)), pl.name },
+ { QStringView(Fields::path), pl.path },
+ { QStringView(Fields::classNames), names } });
+}
+
+bool QmldirFile::iterateDirectSubpaths(const DomItem &self, DirectVisitor visitor) const
+{
+ bool cont = ExternalOwningItem::iterateDirectSubpaths(self, visitor);
+ cont = cont && self.dvValueField(visitor, Fields::uri, uri().toString());
+ cont = cont && self.dvValueField(visitor, Fields::designerSupported, designerSupported());
+ cont = cont && self.dvReferencesField(visitor, Fields::qmltypesFiles, m_qmltypesFilePaths);
+ cont = cont && self.dvWrapField(visitor, Fields::exports, m_exports);
+ cont = cont && self.dvWrapField(visitor, Fields::imports, m_imports);
+ cont = cont && self.dvItemField(visitor, Fields::plugins, [this, &self]() {
+ QStringList cNames = classNames();
+ return self.subListItem(List::fromQListRef<QQmlDirParser::Plugin>(
+ self.pathFromOwner().field(Fields::plugins), m_plugins,
+ [cNames](const DomItem &list, const PathEls::PathComponent &p,
+ const QQmlDirParser::Plugin &plugin) {
+ return list.subDataItem(p, pluginData(plugin, cNames));
+ }));
+ });
+ // add qmlfiles as map because this way they are presented the same way as
+ // the qmlfiles in a directory
+ cont = cont && self.dvItemField(visitor, Fields::qmlFiles, [this, &self]() {
+ const QMap<QString, QString> typeFileMap = qmlFiles();
+ return self.subMapItem(Map(
+ self.pathFromOwner().field(Fields::qmlFiles),
+ [typeFileMap](const DomItem &map, const QString &typeV) {
+ QString path = typeFileMap.value(typeV);
+ if (path.isEmpty())
+ return DomItem();
+ else
+ return map.subReferencesItem(
+ PathEls::Key(typeV),
+ QList<Path>({ Paths::qmlFileObjectPath(path) }));
+ },
+ [typeFileMap](const DomItem &) {
+ return QSet<QString>(typeFileMap.keyBegin(), typeFileMap.keyEnd());
+ },
+ QStringLiteral(u"QList<Reference>")));
+ });
+ cont = cont && self.dvWrapField(visitor, Fields::autoExports, m_autoExports);
+ return cont;
+}
+
+QMap<QString, QString> QmldirFile::qmlFiles() const
+{
+ // add qmlfiles as map because this way they are presented the same way as
+ // the qmlfiles in a directory which gives them as fileName->list of references to files
+ // this is done only to ensure that they are loaded as dependencies
+ QMap<QString, QString> res;
+ for (const auto &e : m_exports)
+ res.insert(e.typeName + QStringLiteral(u"-") + e.version.stringValue(),
+ e.typePath[2].headName());
+ return res;
+}
+
+JsFile::JsFile(
+ const QString &filePath, const QString &code, const QDateTime &lastDataUpdateAt,
+ int derivedFrom)
+ : ExternalOwningItem(filePath, lastDataUpdateAt, Paths::qmlFilePath(filePath), derivedFrom,
+ code)
+{
+ m_engine = std::make_shared<QQmlJS::Engine>();
+ LegacyDirectivesCollector directivesCollector(*this);
+ m_engine->setDirectives(&directivesCollector);
+
+ QQmlJS::Lexer lexer(m_engine.get());
+ lexer.setCode(code, /*lineno = */ 1, /*qmlMode=*/false);
+ QQmlJS::Parser parser(m_engine.get());
+
+ bool isESM = filePath.endsWith(u".mjs", Qt::CaseInsensitive);
+ bool isValid = isESM ? parser.parseModule() : parser.parseProgram();
+ setIsValid(isValid);
+
+ const auto diagnostics = parser.diagnosticMessages();
+ for (const DiagnosticMessage &msg : diagnostics) {
+ addErrorLocal(
+ std::move(myParsingErrors().errorMessage(msg).withFile(filePath).withPath(m_path)));
+ }
+
+ auto astComments = std::make_shared<AstComments>(m_engine);
+
+ CommentCollector collector;
+ collector.collectComments(m_engine, parser.rootNode(), astComments);
+ m_script = std::make_shared<ScriptExpression>(code, m_engine, parser.rootNode(), astComments,
+ isESM ? ScriptExpression::ExpressionType::ESMCode
+ : ScriptExpression::ExpressionType::JSCode);
+}
+
+ErrorGroups JsFile::myParsingErrors()
+{
+ static ErrorGroups res = { { DomItem::domErrorGroup, NewErrorGroup("JsFile"),
+ NewErrorGroup("Parsing") } };
+ return res;
+}
+
+bool JsFile::iterateDirectSubpaths(const DomItem &self, DirectVisitor visitor) const
+{
+ bool cont = ExternalOwningItem::iterateDirectSubpaths(self, visitor);
+ cont = cont && self.dvWrapField(visitor, Fields::fileLocationsTree, m_fileLocationsTree);
+ if (m_script)
+ cont = cont && self.dvItemField(visitor, Fields::expression, [this, &self]() {
+ return self.subOwnerItem(PathEls::Field(Fields::expression), m_script);
+ });
+ return cont;
+}
+
+void JsFile::writeOut(const DomItem &self, OutWriter &ow) const
+{
+ writeOutDirectives(ow);
+ ow.ensureNewline(2);
+ if (DomItem script = self.field(Fields::expression)) {
+ ow.ensureNewline();
+ script.writeOut(ow);
+ }
+}
+
+void JsFile::addFileImport(const QString &jsfile, const QString &module)
+{
+ LegacyImport import;
+ import.fileName = jsfile;
+ import.asIdentifier = module;
+ m_imports.append(std::move(import));
+}
+
+void JsFile::addModuleImport(const QString &uri, const QString &version, const QString &module)
+{
+ LegacyImport import;
+ import.uri = uri;
+ import.version = version;
+ import.asIdentifier = module;
+ m_imports.append(std::move(import));
+}
+
+void JsFile::LegacyPragmaLibrary::writeOut(OutWriter &lw) const
+{
+ lw.write(u".pragma").space().write(u"library").ensureNewline();
+}
+
+void JsFile::LegacyImport::writeOut(OutWriter &lw) const
+{
+ // either filename or module uri must be present
+ Q_ASSERT(!fileName.isEmpty() || !uri.isEmpty());
+
+ lw.write(u".import").space();
+ if (!uri.isEmpty()) {
+ lw.write(uri).space();
+ if (!version.isEmpty()) {
+ lw.write(version).space();
+ }
+ } else {
+ lw.write(u"\"").write(fileName).write(u"\"").space();
+ }
+ lw.writeRegion(AsTokenRegion).space().write(asIdentifier);
+
+ lw.ensureNewline();
+}
+
+/*!
+ * \internal JsFile::writeOutDirectives
+ * \brief Performs writeOut of the .js Directives (.import, .pragma)
+ *
+ * Watch out!
+ * Currently directives in .js files do not have representative AST::Node-s (see QTBUG-119770),
+ * which makes it hard to preserve attached comments during the WriteOut process,
+ * because currently they are being attached to the first AST::Node.
+ * In case when the first AST::Node is absent, they are not collected, hence lost.
+ */
+void JsFile::writeOutDirectives(OutWriter &ow) const
+{
+ if (m_pragmaLibrary.has_value()) {
+ m_pragmaLibrary->writeOut(ow);
+ }
+ for (const auto &import : m_imports) {
+ import.writeOut(ow);
+ }
+}
+
+std::shared_ptr<OwningItem> QmlFile::doCopy(const DomItem &) const
+{
+ auto res = std::make_shared<QmlFile>(*this);
+ return res;
+}
+
+/*!
+ \class QmlFile
+
+ A QmlFile, when loaded in a DomEnvironment that has the DomCreationOption::WithSemanticAnalysis,
+ will be lazily constructed. That means that its member m_lazyMembers is uninitialized, and will
+ only be populated when it is accessed (through a getter, a setter or the DomItem interface).
+
+ The reason for the laziness is that the qqmljsscopes are created lazily and at the same time as
+ the Dom QmlFile representations. So instead of eagerly generating all qqmljsscopes when
+ constructing the Dom, the QmlFile itself becomes lazy and will only be populated on demand at
+ the same time as the corresponding qqmljsscopes.
+
+ The QDeferredFactory<QQmlJSScope> will, when the qqmljsscope is populated, take care of
+ populating all fields of the QmlFile.
+ Therefore, population of the QmlFile is done by populating the qqmljsscope.
+
+*/
+
+QmlFile::QmlFile(
+ const QString &filePath, const QString &code, const QDateTime &lastDataUpdateAt,
+ int derivedFrom, RecoveryOption option)
+ : ExternalOwningItem(filePath, lastDataUpdateAt, Paths::qmlFilePath(filePath), derivedFrom,
+ code),
+ m_engine(new QQmlJS::Engine)
+{
+ QQmlJS::Lexer lexer(m_engine.get());
+ lexer.setCode(code, /*lineno = */ 1, /*qmlMode=*/true);
+ QQmlJS::Parser parser(m_engine.get());
+ if (option == EnableParserRecovery) {
+ parser.setIdentifierInsertionEnabled(true);
+ parser.setIncompleteBindingsEnabled(true);
+ }
+ m_isValid = parser.parse();
+ const auto diagnostics = parser.diagnosticMessages();
+ for (const DiagnosticMessage &msg : diagnostics) {
+ addErrorLocal(
+ std::move(myParsingErrors().errorMessage(msg).withFile(filePath).withPath(m_path)));
+ }
+ m_ast = parser.ast();
+}
+
+ErrorGroups QmlFile::myParsingErrors()
+{
+ static ErrorGroups res = { { DomItem::domErrorGroup, NewErrorGroup("QmlFile"),
+ NewErrorGroup("Parsing") } };
+ return res;
+}
+
+bool QmlFile::iterateDirectSubpaths(const DomItem &self, DirectVisitor visitor) const
+{
+ auto &members = lazyMembers();
+ bool cont = ExternalOwningItem::iterateDirectSubpaths(self, visitor);
+ cont = cont && self.dvWrapField(visitor, Fields::components, members.m_components);
+ cont = cont && self.dvWrapField(visitor, Fields::pragmas, members.m_pragmas);
+ cont = cont && self.dvWrapField(visitor, Fields::imports, members.m_imports);
+ cont = cont && self.dvWrapField(visitor, Fields::importScope, members.m_importScope);
+ cont = cont
+ && self.dvWrapField(visitor, Fields::fileLocationsTree, members.m_fileLocationsTree);
+ cont = cont && self.dvWrapField(visitor, Fields::comments, members.m_comments);
+ cont = cont && self.dvWrapField(visitor, Fields::astComments, members.m_astComments);
+ return cont;
+}
+
+DomItem QmlFile::field(const DomItem &self, QStringView name) const
+{
+ ensurePopulated();
+ if (name == Fields::components)
+ return self.wrapField(Fields::components, lazyMembers().m_components);
+ return DomBase::field(self, name);
+}
+
+void QmlFile::addError(const DomItem &self, ErrorMessage &&msg)
+{
+ self.containingObject().addError(std::move(msg));
+}
+
+void QmlFile::writeOut(const DomItem &self, OutWriter &ow) const
+{
+ ensurePopulated();
+ for (const DomItem &p : self.field(Fields::pragmas).values()) {
+ p.writeOut(ow);
+ }
+ for (auto i : self.field(Fields::imports).values()) {
+ i.writeOut(ow);
+ }
+ ow.ensureNewline(2);
+ DomItem mainC = self.field(Fields::components).key(QString()).index(0);
+ mainC.writeOut(ow);
+}
+
+std::shared_ptr<OwningItem> GlobalScope::doCopy(const DomItem &self) const
+{
+ auto res = std::make_shared<GlobalScope>(
+ canonicalFilePath(self), lastDataUpdateAt(), revision());
+ return res;
+}
+
+bool GlobalScope::iterateDirectSubpaths(const DomItem &self, DirectVisitor visitor) const
+{
+ bool cont = ExternalOwningItem::iterateDirectSubpaths(self, visitor);
+ return cont;
+}
+
+void QmltypesFile::ensureInModuleIndex(const DomItem &self) const
+{
+ auto it = m_uris.begin();
+ auto end = m_uris.end();
+ DomItem env = self.environment();
+ if (std::shared_ptr<DomEnvironment> envPtr = env.ownerAs<DomEnvironment>()) {
+ while (it != end) {
+ QString uri = it.key();
+ for (int majorV : it.value()) {
+ auto mIndex = envPtr->moduleIndexWithUri(env, uri, majorV, EnvLookup::Normal,
+ Changeable::Writable);
+ mIndex->addQmltypeFilePath(self.canonicalPath());
+ }
+ ++it;
+ }
+ }
+}
+
+bool QmltypesFile::iterateDirectSubpaths(const DomItem &self, DirectVisitor visitor) const
+{
+ bool cont = ExternalOwningItem::iterateDirectSubpaths(self, visitor);
+ cont = cont && self.dvWrapField(visitor, Fields::components, m_components);
+ cont = cont && self.dvWrapField(visitor, Fields::exports, m_exports);
+ cont = cont && self.dvItemField(visitor, Fields::uris, [this, &self]() {
+ return self.subMapItem(Map::fromMapRef<QSet<int>>(
+ self.pathFromOwner().field(Fields::uris), m_uris,
+ [](const DomItem &map, const PathEls::PathComponent &p, const QSet<int> &el) {
+ QList<int> l(el.cbegin(), el.cend());
+ std::sort(l.begin(), l.end());
+ return map.subListItem(
+ List::fromQList<int>(map.pathFromOwner().appendComponent(p), l,
+ [](const DomItem &list, const PathEls::PathComponent &p,
+ int el) { return list.subDataItem(p, el); }));
+ }));
+ });
+ cont = cont && self.dvWrapField(visitor, Fields::imports, m_imports);
+ return cont;
+}
+
+QmlDirectory::QmlDirectory(
+ const QString &filePath, const QStringList &dirList, const QDateTime &lastDataUpdateAt,
+ int derivedFrom)
+ : ExternalOwningItem(filePath, lastDataUpdateAt, Paths::qmlDirectoryPath(filePath), derivedFrom,
+ dirList.join(QLatin1Char('\n')))
+{
+ for (const QString &f : dirList) {
+ addQmlFilePath(f);
+ }
+}
+
+bool QmlDirectory::iterateDirectSubpaths(const DomItem &self, DirectVisitor visitor) const
+{
+ bool cont = ExternalOwningItem::iterateDirectSubpaths(self, visitor);
+ cont = cont && self.dvWrapField(visitor, Fields::exports, m_exports);
+ cont = cont && self.dvItemField(visitor, Fields::qmlFiles, [this, &self]() -> DomItem {
+ QDir baseDir(canonicalFilePath());
+ return self.subMapItem(Map(
+ self.pathFromOwner().field(Fields::qmlFiles),
+ [this, baseDir](const DomItem &map, const QString &key) -> DomItem {
+ QList<Path> res;
+ auto it = m_qmlFiles.find(key);
+ while (it != m_qmlFiles.end() && it.key() == key) {
+ res.append(Paths::qmlFilePath(
+ QFileInfo(baseDir.filePath(it.value())).canonicalFilePath()));
+ ++it;
+ }
+ return map.subReferencesItem(PathEls::Key(key), res);
+ },
+ [this](const DomItem &) {
+ auto keys = m_qmlFiles.keys();
+ return QSet<QString>(keys.begin(), keys.end());
+ },
+ u"List<Reference>"_s));
+ });
+ return cont;
+}
+
+bool QmlDirectory::addQmlFilePath(const QString &relativePath)
+{
+ static const QRegularExpression qmlFileRegularExpression{
+ QRegularExpression::anchoredPattern(
+ uR"((?<compName>[a-zA-z0-9_]+)\.(?:qml|qmlannotation|ui\.qml))")
+ };
+ QRegularExpressionMatch m = qmlFileRegularExpression.match(relativePath);
+ if (m.hasMatch() && !m_qmlFiles.values(m.captured(u"compName")).contains(relativePath)) {
+ m_qmlFiles.insert(m.captured(u"compName"), relativePath);
+ Export e;
+ QDir dir(canonicalFilePath());
+ QFileInfo fInfo(dir.filePath(relativePath));
+ e.exportSourcePath = canonicalPath();
+ e.typeName = m.captured(u"compName");
+ e.typePath = Paths::qmlFileObjectPath(fInfo.canonicalFilePath());
+ e.uri = QLatin1String("file://") + canonicalFilePath();
+ m_exports.insert(e.typeName, e);
+ return true;
+ }
+ return false;
+}
+
} // end namespace Dom
} // end namespace QQmlJS
diff --git a/src/qmldom/qqmldomexternalitems_p.h b/src/qmldom/qqmldomexternalitems_p.h
index fcb15ba2ac..1aa9d765cb 100644
--- a/src/qmldom/qqmldomexternalitems_p.h
+++ b/src/qmldom/qqmldomexternalitems_p.h
@@ -1,40 +1,6 @@
-/****************************************************************************
-**
-** 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$
-**/
+// Copyright (C) 2020 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+
#ifndef QQMLDOMEXTERNALITEMS_P_H
#define QQMLDOMEXTERNALITEMS_P_H
@@ -50,13 +16,19 @@
//
#include "qqmldomitem_p.h"
+#include "qqmldomelements_p.h"
+#include "qqmldommoduleindex_p.h"
+#include "qqmldomcomments_p.h"
#include <QtQml/private/qqmljsast_p.h>
#include <QtQml/private/qqmljsengine_p.h>
#include <QtQml/private/qqmldirparser_p.h>
+#include <QtQmlCompiler/private/qqmljstyperesolver_p.h>
#include <QtCore/QMetaType>
+#include <QtCore/qregularexpression.h>
#include <limits>
+#include <memory>
Q_DECLARE_METATYPE(QQmlDirParser::Plugin)
@@ -77,18 +49,41 @@ Every owning item has a file or directory it refers to.
*/
class QMLDOM_EXPORT ExternalOwningItem: public OwningItem {
public:
- ExternalOwningItem(QString filePath, QDateTime lastDataUpdateAt, Path pathFromTop, int derivedFrom=0);
- ExternalOwningItem(const ExternalOwningItem &o);
+ ExternalOwningItem(
+ const QString &filePath, const QDateTime &lastDataUpdateAt, const Path &pathFromTop,
+ int derivedFrom = 0, const QString &code = QString());
+ ExternalOwningItem(const ExternalOwningItem &o) = default;
QString canonicalFilePath(const DomItem &) const override;
QString canonicalFilePath() const;
Path canonicalPath(const DomItem &) const override;
Path canonicalPath() const;
- bool iterateDirectSubpaths(DomItem &self, std::function<bool (Path, DomItem &)> visitor) override {
+ bool iterateDirectSubpaths(const DomItem &self, DirectVisitor visitor) const override
+ {
bool cont = OwningItem::iterateDirectSubpaths(self, visitor);
- cont = cont && self.subDataField(Fields::canonicalFilePath, canonicalFilePath()).visit(visitor);
- cont = cont && self.subDataField(Fields::isValid, isValid()).visit(visitor);
-// if (!code().isNull())
-// cont = cont && self.subDataField(Fields::code, code()).visit(visitor);
+ cont = cont && self.dvValueLazyField(visitor, Fields::canonicalFilePath, [this]() {
+ return canonicalFilePath();
+ });
+ cont = cont
+ && self.dvValueLazyField(visitor, Fields::isValid, [this]() { return isValid(); });
+ if (!code().isNull())
+ cont = cont
+ && self.dvValueLazyField(visitor, Fields::code, [this]() { return code(); });
+ return cont;
+ }
+
+ bool iterateSubOwners(const DomItem &self, function_ref<bool(const DomItem &owner)> visitor) override
+ {
+ bool cont = OwningItem::iterateSubOwners(self, visitor);
+ cont = cont && self.field(Fields::components).visitKeys([visitor](const QString &, const DomItem &comps) {
+ return comps.visitIndexes([visitor](const DomItem &comp) {
+ return comp.field(Fields::objects).visitIndexes([visitor](const DomItem &qmlObj) {
+ if (const QmlObject *qmlObjPtr = qmlObj.as<QmlObject>())
+ return qmlObjPtr->iterateSubOwners(qmlObj, visitor);
+ Q_ASSERT(false);
+ return true;
+ });
+ });
+ });
return cont;
}
@@ -101,13 +96,503 @@ public:
m_isValid = val;
}
// null code means invalid
- virtual QString code() const { return QString(); }
+ const QString &code() const { return m_code; }
+
protected:
QString m_canonicalFilePath;
+ QString m_code;
Path m_path;
bool m_isValid = false;
};
+class QMLDOM_EXPORT QmlDirectory final : public ExternalOwningItem
+{
+protected:
+ std::shared_ptr<OwningItem> doCopy(const DomItem &) const override
+ {
+ return std::make_shared<QmlDirectory>(*this);
+ }
+
+public:
+ constexpr static DomType kindValue = DomType::QmlDirectory;
+ DomType kind() const override { return kindValue; }
+ QmlDirectory(
+ const QString &filePath = QString(), const QStringList &dirList = QStringList(),
+ const QDateTime &lastDataUpdateAt = QDateTime::fromMSecsSinceEpoch(0, QTimeZone::UTC),
+ int derivedFrom = 0);
+ QmlDirectory(const QmlDirectory &o) = default;
+
+ std::shared_ptr<QmlDirectory> makeCopy(const DomItem &self) const
+ {
+ return std::static_pointer_cast<QmlDirectory>(doCopy(self));
+ }
+
+ bool iterateDirectSubpaths(const DomItem &self, DirectVisitor visitor) const override;
+
+ const QMultiMap<QString, Export> &exports() const & { return m_exports; }
+
+ const QMultiMap<QString, QString> &qmlFiles() const & { return m_qmlFiles; }
+
+ bool addQmlFilePath(const QString &relativePath);
+
+private:
+ QMultiMap<QString, Export> m_exports;
+ QMultiMap<QString, QString> m_qmlFiles;
+};
+
+class QMLDOM_EXPORT QmldirFile final : public ExternalOwningItem
+{
+ Q_DECLARE_TR_FUNCTIONS(QmldirFile)
+protected:
+ std::shared_ptr<OwningItem> doCopy(const DomItem &) const override
+ {
+ auto copy = std::make_shared<QmldirFile>(*this);
+ return copy;
+ }
+
+public:
+ constexpr static DomType kindValue = DomType::QmldirFile;
+ DomType kind() const override { return kindValue; }
+
+ static ErrorGroups myParsingErrors();
+
+ QmldirFile(
+ const QString &filePath = QString(), const QString &code = QString(),
+ const QDateTime &lastDataUpdateAt = QDateTime::fromMSecsSinceEpoch(0, QTimeZone::UTC),
+ int derivedFrom = 0)
+ : ExternalOwningItem(filePath, lastDataUpdateAt, Paths::qmldirFilePath(filePath),
+ derivedFrom, code)
+ {
+ }
+ QmldirFile(const QmldirFile &o) = default;
+
+ static std::shared_ptr<QmldirFile> fromPathAndCode(const QString &path, const QString &code);
+
+ std::shared_ptr<QmldirFile> makeCopy(const DomItem &self) const
+ {
+ return std::static_pointer_cast<QmldirFile>(doCopy(self));
+ }
+
+ bool iterateDirectSubpaths(const DomItem &self, DirectVisitor visitor) const override;
+
+ QmlUri uri() const { return m_uri; }
+
+ const QSet<int> &majorVersions() const & { return m_majorVersions; }
+
+ const QMultiMap<QString, Export> &exports() const & { return m_exports; }
+
+ const QList<Import> &imports() const & { return m_imports; }
+
+ const QList<Path> &qmltypesFilePaths() const & { return m_qmltypesFilePaths; }
+
+ QMap<QString, QString> qmlFiles() const;
+
+ bool designerSupported() const { return m_qmldir.designerSupported(); }
+
+ QStringList classNames() const { return m_qmldir.classNames(); }
+
+ QList<ModuleAutoExport> autoExports() const;
+ void setAutoExports(const QList<ModuleAutoExport> &autoExport);
+
+ void ensureInModuleIndex(const DomItem &self, const QString &uri) const;
+
+private:
+ void parse();
+ void setFromQmldir();
+
+ QmlUri m_uri;
+ QSet<int> m_majorVersions;
+ QQmlDirParser m_qmldir;
+ QList<QQmlDirParser::Plugin> m_plugins;
+ QList<Import> m_imports;
+ QList<ModuleAutoExport> m_autoExports;
+ QMultiMap<QString, Export> m_exports;
+ QList<Path> m_qmltypesFilePaths;
+};
+
+class QMLDOM_EXPORT JsFile final : public ExternalOwningItem
+{
+protected:
+ std::shared_ptr<OwningItem> doCopy(const DomItem &) const override
+ {
+ auto copy = std::make_shared<JsFile>(*this);
+ return copy;
+ }
+
+public:
+ constexpr static DomType kindValue = DomType::JsFile;
+ DomType kind() const override { return kindValue; }
+ JsFile(const QString &filePath = QString(),
+ const QDateTime &lastDataUpdateAt = QDateTime::fromMSecsSinceEpoch(0, QTimeZone::UTC),
+ const Path &pathFromTop = Path(), int derivedFrom = 0)
+ : ExternalOwningItem(filePath, lastDataUpdateAt, pathFromTop, derivedFrom)
+ {
+ }
+ JsFile(const QString &filePath = QString(), const QString &code = QString(),
+ const QDateTime &lastDataUpdateAt = QDateTime::fromMSecsSinceEpoch(0, QTimeZone::UTC),
+ int derivedFrom = 0);
+ JsFile(const JsFile &o) = default;
+
+ std::shared_ptr<JsFile> makeCopy(const DomItem &self) const
+ {
+ return std::static_pointer_cast<JsFile>(doCopy(self));
+ }
+
+ bool iterateDirectSubpaths(const DomItem &self, DirectVisitor) const
+ override; // iterates the *direct* subpaths, returns false if a quick end was requested
+
+ std::shared_ptr<QQmlJS::Engine> engine() const { return m_engine; }
+ JsResource rootComponent() const { return m_rootComponent; }
+ void setFileLocationsTree(const FileLocations::Tree &v) { m_fileLocationsTree = std::move(v); }
+
+ static ErrorGroups myParsingErrors();
+
+ void writeOut(const DomItem &self, OutWriter &lw) const override;
+ void setExpression(const std::shared_ptr<ScriptExpression> &script) { m_script = script; }
+
+ void initPragmaLibrary() { m_pragmaLibrary = LegacyPragmaLibrary{}; };
+ void addFileImport(const QString &jsfile, const QString &module);
+ void addModuleImport(const QString &uri, const QString &version, const QString &module);
+
+private:
+ void writeOutDirectives(OutWriter &lw) const;
+
+ /*
+ Entities with Legacy prefix are here to support formatting of the discouraged
+ .import, .pragma directives in .js files.
+ Taking into account that usage of these directives is discouraged and
+ the fact that current usecase is limited to the formatting of .js, it's arguably should not
+ be exposed and kept private.
+
+ LegacyPragma corresponds to the only one existing .pragma library
+
+ LegacyImport is capable of representing the following import statements:
+ .import T_STRING_LITERAL as T_IDENTIFIER
+ .import T_IDENTIFIER (. T_IDENTIFIER)* (T_VERSION_NUMBER (. T_VERSION_NUMBER)?)? as T_IDENTIFIER
+
+ LegacyDirectivesCollector is a workaround for collecting those directives.
+ At the moment of writing .import, .pragma in .js files do not have corresponding
+ representative AST::Node-s. Collecting of those is happening during the lexing
+ */
+
+ struct LegacyPragmaLibrary
+ {
+ void writeOut(OutWriter &lw) const;
+ };
+
+ struct LegacyImport
+ {
+ QString fileName; // file import
+ QString uri; // module import
+ QString version; // used for module import
+ QString asIdentifier; // .import ... as T_Identifier
+
+ void writeOut(OutWriter &lw) const;
+ };
+
+ class LegacyDirectivesCollector : public QQmlJS::Directives
+ {
+ public:
+ LegacyDirectivesCollector(JsFile &file) : m_file(file){};
+
+ void pragmaLibrary() override { m_file.initPragmaLibrary(); };
+ void importFile(const QString &jsfile, const QString &module, int, int) override
+ {
+ m_file.addFileImport(jsfile, module);
+ };
+ void importModule(const QString &uri, const QString &version, const QString &module, int,
+ int) override
+ {
+ m_file.addModuleImport(uri, version, module);
+ };
+
+ private:
+ JsFile &m_file;
+ };
+
+private:
+ std::shared_ptr<QQmlJS::Engine> m_engine;
+ std::optional<LegacyPragmaLibrary> m_pragmaLibrary = std::nullopt;
+ QList<LegacyImport> m_imports;
+ std::shared_ptr<ScriptExpression> m_script;
+ JsResource m_rootComponent;
+ FileLocations::Tree m_fileLocationsTree;
+};
+
+class QMLDOM_EXPORT QmlFile final : public ExternalOwningItem
+{
+protected:
+ std::shared_ptr<OwningItem> doCopy(const DomItem &self) const override;
+
+public:
+ constexpr static DomType kindValue = DomType::QmlFile;
+ DomType kind() const override { return kindValue; }
+
+ enum RecoveryOption { DisableParserRecovery, EnableParserRecovery };
+
+ QmlFile(const QString &filePath = QString(), const QString &code = QString(),
+ const QDateTime &lastDataUpdate = QDateTime::fromMSecsSinceEpoch(0, QTimeZone::UTC),
+ int derivedFrom = 0, RecoveryOption option = DisableParserRecovery);
+ static ErrorGroups myParsingErrors();
+ bool iterateDirectSubpaths(const DomItem &self, DirectVisitor) const
+ override; // iterates the *direct* subpaths, returns false if a quick end was requested
+ DomItem field(const DomItem &self, QStringView name) const override;
+ std::shared_ptr<QmlFile> makeCopy(const DomItem &self) const
+ {
+ return std::static_pointer_cast<QmlFile>(doCopy(self));
+ }
+ void addError(const DomItem &self, ErrorMessage &&msg) override;
+
+ const QMultiMap<QString, QmlComponent> &components() const &
+ {
+ return lazyMembers().m_components;
+ }
+ void setComponents(const QMultiMap<QString, QmlComponent> &components)
+ {
+ lazyMembers().m_components = components;
+ }
+ Path addComponent(const QmlComponent &component, AddOption option = AddOption::Overwrite,
+ QmlComponent **cPtr = nullptr)
+ {
+ QStringList nameEls = component.name().split(QChar::fromLatin1('.'));
+ QString key = nameEls.mid(1).join(QChar::fromLatin1('.'));
+ return insertUpdatableElementInMultiMap(Path::Field(Fields::components), lazyMembers().m_components,
+ key, component, option, cPtr);
+ }
+
+ void writeOut(const DomItem &self, OutWriter &lw) const override;
+
+ AST::UiProgram *ast() const
+ {
+ return m_ast; // avoid making it public? would make moving away from it easier
+ }
+ const QList<Import> &imports() const &
+ {
+ return lazyMembers().m_imports;
+ }
+ void setImports(const QList<Import> &imports) { lazyMembers().m_imports = imports; }
+ Path addImport(const Import &i)
+ {
+ auto &members = lazyMembers();
+ index_type idx = index_type(members.m_imports.size());
+ members.m_imports.append(i);
+ if (i.uri.isModule()) {
+ members.m_importScope.addImport((i.importId.isEmpty()
+ ? QStringList()
+ : i.importId.split(QChar::fromLatin1('.'))),
+ i.importedPath());
+ } else {
+ QString path = i.uri.absoluteLocalPath(canonicalFilePath());
+ if (!path.isEmpty())
+ members.m_importScope.addImport(
+ (i.importId.isEmpty() ? QStringList()
+ : i.importId.split(QChar::fromLatin1('.'))),
+ Paths::qmlDirPath(path));
+ }
+ return Path::Field(Fields::imports).index(idx);
+ }
+ std::shared_ptr<QQmlJS::Engine> engine() const { return m_engine; }
+ RegionComments &comments() { return lazyMembers().m_comments; }
+ std::shared_ptr<AstComments> astComments() const { return lazyMembers().m_astComments; }
+ void setAstComments(const std::shared_ptr<AstComments> &comm) { lazyMembers().m_astComments = comm; }
+ FileLocations::Tree fileLocationsTree() const { return lazyMembers().m_fileLocationsTree; }
+ void setFileLocationsTree(const FileLocations::Tree &v) { lazyMembers().m_fileLocationsTree = v; }
+ const QList<Pragma> &pragmas() const & { return lazyMembers().m_pragmas; }
+ void setPragmas(QList<Pragma> pragmas) { lazyMembers().m_pragmas = pragmas; }
+ Path addPragma(const Pragma &pragma)
+ {
+ auto &members = lazyMembers();
+ int idx = members.m_pragmas.size();
+ members.m_pragmas.append(pragma);
+ return Path::Field(Fields::pragmas).index(idx);
+ }
+ ImportScope &importScope() { return lazyMembers().m_importScope; }
+ const ImportScope &importScope() const { return lazyMembers().m_importScope; }
+
+ std::shared_ptr<QQmlJSTypeResolver> typeResolver() const
+ {
+ return lazyMembers().m_typeResolver;
+ }
+ void setTypeResolverWithDependencies(const std::shared_ptr<QQmlJSTypeResolver> &typeResolver,
+ const QQmlJSTypeResolverDependencies &dependencies)
+ {
+ auto &members = lazyMembers();
+ members.m_typeResolver = typeResolver;
+ members.m_typeResolverDependencies = dependencies;
+ }
+
+ DomCreationOptions creationOptions() const { return lazyMembers().m_creationOptions; }
+
+ void setHandleForPopulation(const QQmlJSScope::ConstPtr &scope)
+ {
+ m_handleForPopulation = scope;
+ }
+
+
+private:
+ // The lazy parts of QmlFile are inside of QmlFileLazy.
+ struct QmlFileLazy
+ {
+ QmlFileLazy(FileLocations::Tree fileLocationsTree, AstComments *astComments)
+ : m_fileLocationsTree(fileLocationsTree), m_astComments(astComments)
+ {
+ }
+ RegionComments m_comments;
+ QMultiMap<QString, QmlComponent> m_components;
+ QList<Pragma> m_pragmas;
+ QList<Import> m_imports;
+ ImportScope m_importScope;
+ FileLocations::Tree m_fileLocationsTree;
+ std::shared_ptr<AstComments> m_astComments;
+ DomCreationOptions m_creationOptions;
+ std::shared_ptr<QQmlJSTypeResolver> m_typeResolver;
+ QQmlJSTypeResolverDependencies m_typeResolverDependencies;
+ };
+ friend class QQmlDomAstCreator;
+ AST::UiProgram *m_ast; // avoid? would make moving away from it easier
+ std::shared_ptr<Engine> m_engine;
+ QQmlJSScope::ConstPtr m_handleForPopulation;
+ mutable std::optional<QmlFileLazy> m_lazyMembers;
+
+ void ensurePopulated() const
+ {
+ if (m_lazyMembers)
+ return;
+
+ m_lazyMembers.emplace(FileLocations::createTree(canonicalPath()), new AstComments(m_engine));
+
+ // populate via the QQmlJSScope by accessing the (lazy) pointer
+ if (m_handleForPopulation.factory()) {
+ // silence no-discard attribute:
+ Q_UNUSED(m_handleForPopulation.data());
+ }
+ }
+ const QmlFileLazy &lazyMembers() const
+ {
+ ensurePopulated();
+ return *m_lazyMembers;
+ }
+ QmlFileLazy &lazyMembers()
+ {
+ ensurePopulated();
+ return *m_lazyMembers;
+ }
+};
+
+class QMLDOM_EXPORT QmltypesFile final : public ExternalOwningItem
+{
+protected:
+ std::shared_ptr<OwningItem> doCopy(const DomItem &) const override
+ {
+ auto res = std::make_shared<QmltypesFile>(*this);
+ return res;
+ }
+
+public:
+ constexpr static DomType kindValue = DomType::QmltypesFile;
+ DomType kind() const override { return kindValue; }
+
+ QmltypesFile(
+ const QString &filePath = QString(), const QString &code = QString(),
+ const QDateTime &lastDataUpdateAt = QDateTime::fromMSecsSinceEpoch(0, QTimeZone::UTC),
+ int derivedFrom = 0)
+ : ExternalOwningItem(filePath, lastDataUpdateAt, Paths::qmltypesFilePath(filePath),
+ derivedFrom, code)
+ {
+ }
+
+ QmltypesFile(const QmltypesFile &o) = default;
+
+ void ensureInModuleIndex(const DomItem &self) const;
+
+ bool iterateDirectSubpaths(const DomItem &self, DirectVisitor) const override;
+ std::shared_ptr<QmltypesFile> makeCopy(const DomItem &self) const
+ {
+ return std::static_pointer_cast<QmltypesFile>(doCopy(self));
+ }
+
+ void addImport(const Import i)
+ { // builder only: not threadsafe...
+ m_imports.append(i);
+ }
+ const QList<Import> &imports() const & { return m_imports; }
+ const QMultiMap<QString, QmltypesComponent> &components() const & { return m_components; }
+ void setComponents(QMultiMap<QString, QmltypesComponent> c) { m_components = std::move(c); }
+ Path addComponent(const QmltypesComponent &comp, AddOption option = AddOption::Overwrite,
+ QmltypesComponent **cPtr = nullptr)
+ {
+ for (const Export &e : comp.exports())
+ addExport(e);
+ return insertUpdatableElementInMultiMap(Path::Field(u"components"), m_components,
+ comp.name(), comp, option, cPtr);
+ }
+ const QMultiMap<QString, Export> &exports() const & { return m_exports; }
+ void setExports(QMultiMap<QString, Export> e) { m_exports = e; }
+ Path addExport(const Export &e)
+ {
+ index_type i = m_exports.values(e.typeName).size();
+ m_exports.insert(e.typeName, e);
+ addUri(e.uri, e.version.majorVersion);
+ return canonicalPath().field(Fields::exports).index(i);
+ }
+
+ const QMap<QString, QSet<int>> &uris() const & { return m_uris; }
+ void addUri(const QString &uri, int majorVersion)
+ {
+ QSet<int> &v = m_uris[uri];
+ if (!v.contains(majorVersion)) {
+ v.insert(majorVersion);
+ }
+ }
+
+private:
+ QList<Import> m_imports;
+ QMultiMap<QString, QmltypesComponent> m_components;
+ QMultiMap<QString, Export> m_exports;
+ QMap<QString, QSet<int>> m_uris;
+};
+
+class QMLDOM_EXPORT GlobalScope final : public ExternalOwningItem
+{
+protected:
+ std::shared_ptr<OwningItem> doCopy(const DomItem &) const override;
+
+public:
+ constexpr static DomType kindValue = DomType::GlobalScope;
+ DomType kind() const override { return kindValue; }
+
+ GlobalScope(
+ const QString &filePath = QString(),
+ const QDateTime &lastDataUpdateAt = QDateTime::fromMSecsSinceEpoch(0, QTimeZone::UTC),
+ int derivedFrom = 0)
+ : ExternalOwningItem(filePath, lastDataUpdateAt, Paths::globalScopePath(filePath),
+ derivedFrom)
+ {
+ setIsValid(true);
+ }
+
+ bool iterateDirectSubpaths(const DomItem &self, DirectVisitor visitor) const override;
+ std::shared_ptr<GlobalScope> makeCopy(const DomItem &self) const
+ {
+ return std::static_pointer_cast<GlobalScope>(doCopy(self));
+ }
+ QString name() const { return m_name; }
+ Language language() const { return m_language; }
+ GlobalComponent rootComponent() const { return m_rootComponent; }
+ void setName(const QString &name) { m_name = name; }
+ void setLanguage(Language language) { m_language = language; }
+ void setRootComponent(const GlobalComponent &ob)
+ {
+ m_rootComponent = ob;
+ m_rootComponent.updatePathFromOwner(Path::Field(Fields::rootComponent));
+ }
+
+private:
+ QString m_name;
+ Language m_language;
+ GlobalComponent m_rootComponent;
+};
+
} // end namespace Dom
} // end namespace QQmlJS
QT_END_NAMESPACE
diff --git a/src/qmldom/qqmldomfieldfilter.cpp b/src/qmldom/qqmldomfieldfilter.cpp
new file mode 100644
index 0000000000..67b33bbb7e
--- /dev/null
+++ b/src/qmldom/qqmldomfieldfilter.cpp
@@ -0,0 +1,258 @@
+// Copyright (C) 2020 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+
+#include "qqmldomfieldfilter_p.h"
+#include "qqmldompath_p.h"
+#include "qqmldomitem_p.h"
+#include "QtCore/qglobal.h"
+
+QT_BEGIN_NAMESPACE
+
+namespace QQmlJS {
+namespace Dom {
+
+/*!
+\internal
+\class QQmljs::Dom::FieldFilter
+
+\brief Class that represent a filter on DomItem, when dumping or comparing
+
+DomItem can be duped or compared, but often one is interested only in a subset
+of them, FieldFilter is a simple way to select a subset of them.
+It uses two basic elements: the type of the object (internalKind) and the
+name of fields.
+
+A basic filter can be represented by <op><typeName>:<fieldName> or <op><fieldName>
+where op is either + or - (if the matching elements should be added or removed)
+Both typeName and fieldName can be the empty string (meaning any value matches).
+
+Basic filters are ordered from the most specific to the least specific as follow:
+type+field > type > field > empty.
+When combining several filters the most specific always wins, so
+-code,+ScriptExpression:code is the same as +ScriptExpression:code,-code and means
+that normally the field code is not outputted but for a ScriptExpression DomItem
+it is.
+
+It is possible to get the string representation of the current filter with
+FieldFilter::describeFieldsFilter(), and change the current filter with
+FieldFilter::addFilter(), but after it one should call FieldFilter::setFiltred()
+to ensure that the internal cache used to speed up comparisons is correct.
+*/
+
+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()(const DomItem &obj, const Path &p, const DomItem &i) const
+{
+ if (p)
+ return this->operator()(obj, p.component(0), i);
+ else
+ return this->operator()(obj, PathEls::Empty(), i);
+}
+
+bool FieldFilter::operator()(const DomItem &base, const PathEls::PathComponent &c, const 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(const QString &fFields)
+{
+ // parses a base filter of the form <op><typeName>:<fieldName> or <op><fieldName>
+ // as described in this class documentation
+ QRegularExpression fieldRe(QRegularExpression::anchoredPattern(QStringLiteral(
+ uR"((?<op>[-+])?(?:(?<type>[a-zA-Z0-9_]*):)?(?<field>[a-zA-Z0-9_]*))")));
+ for (const QString &fField : fFields.split(QLatin1Char(','))) {
+ QRegularExpressionMatch m = fieldRe.matchView(fField);
+ if (m.hasMatch()) {
+ if (m.capturedView(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::noFilter()
+{
+ return FieldFilter{ {}, {} };
+}
+
+FieldFilter FieldFilter::defaultFilter()
+{
+ QMultiMap<QString, QString> fieldFilterAdd { { QLatin1String("ScriptExpression"),
+ QLatin1String("code") } };
+ QMultiMap<QString, QString> fieldFilterRemove {
+ { QString(), QString::fromUtf16(Fields::code) },
+ { QString(), QString::fromUtf16(Fields::postCode) },
+ { QString(), QString::fromUtf16(Fields::preCode) },
+ { QString(), QString::fromUtf16(Fields::importScope) },
+ { QString(), QString::fromUtf16(Fields::fileLocationsTree) },
+ { QString(), QString::fromUtf16(Fields::astComments) },
+ { QString(), QString::fromUtf16(Fields::comments) },
+ { QString(), QString::fromUtf16(Fields::exports) },
+ { QString(), QString::fromUtf16(Fields::propertyInfos) },
+ { QLatin1String("AttachedInfo"), QString::fromUtf16(Fields::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") },
+ { QString(), QLatin1String("location") },
+ { QLatin1String("ScriptExpression"), QLatin1String("localOffset") },
+ { QLatin1String("ScriptExpression"), QLatin1String("preCode") },
+ { QLatin1String("ScriptExpression"), QLatin1String("postCode") },
+ { QLatin1String("AttachedInfo"), QLatin1String("parent") },
+ { QLatin1String("Reference"), QLatin1String("get") },
+ { QLatin1String("QmlComponent"), QLatin1String("ids") },
+ { QLatin1String("QmlObject"), QLatin1String("prototypes") }
+ };
+ 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("QmlComponent"), QLatin1String("ids") },
+ { QLatin1String("QmlObject"), QLatin1String("prototypes") },
+ { 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("QmlComponent"), QLatin1String("ids") },
+ { QLatin1String("QmlObject"), QLatin1String("prototypes") },
+ { QLatin1String(), QLatin1String("code") },
+ { QLatin1String("ScriptExpression"), QLatin1String("localOffset") },
+ { QLatin1String("AttachedInfo"), QLatin1String("parent") },
+ { QString(), QLatin1String("fileLocationsTree") },
+ { QString(), QLatin1String("preCode") },
+ { QString(), QLatin1String("postCode") },
+ { QString(), QLatin1String("comments") },
+ { QString(), QLatin1String("preCommentLocations") },
+ { QString(), QLatin1String("postCommentLocations") },
+ { QString(), QLatin1String("astComments") },
+ { QString(), QLatin1String("location") }
+ };
+ 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 unknown type " << s << " will be ignored";
+ }
+ }
+}
+
+} // end namespace Dom
+} // end namespace QQmlJS
+
+QT_END_NAMESPACE
+
+#include "moc_qqmldomfieldfilter_p.cpp"
diff --git a/src/qmldom/qqmldomfieldfilter_p.h b/src/qmldom/qqmldomfieldfilter_p.h
new file mode 100644
index 0000000000..8ee5caa2c4
--- /dev/null
+++ b/src/qmldom/qqmldomfieldfilter_p.h
@@ -0,0 +1,69 @@
+// Copyright (C) 2020 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+
+#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_fwd_p.h"
+#include "qqmldom_global.h"
+#include "qqmldompath_p.h"
+
+#include <QtCore/qobject.h>
+#include <QtCore/qmap.h>
+#include <QtCore/qset.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(const QString &f);
+ bool operator()(const DomItem &, const Path &, const DomItem &) const;
+ bool operator()(const DomItem &, const PathEls::PathComponent &c, const DomItem &) const;
+ static FieldFilter noFilter();
+ 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..82f2aca496
--- /dev/null
+++ b/src/qmldom/qqmldomfilewriter.cpp
@@ -0,0 +1,135 @@
+// Copyright (C) 2021 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+
+#include "qqmldomfilewriter_p.h"
+#include <QtCore/QRandomGenerator>
+#include <QtCore/QScopeGuard>
+
+QT_BEGIN_NAMESPACE
+namespace QQmlJS {
+namespace Dom {
+
+FileWriter::Status FileWriter::write(const 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
+
+#include "moc_qqmldomfilewriter_p.cpp"
diff --git a/src/qmldom/qqmldomfilewriter_p.h b/src/qmldom/qqmldomfilewriter_p.h
new file mode 100644
index 0000000000..d9987f3f7a
--- /dev/null
+++ b/src/qmldom/qqmldomfilewriter_p.h
@@ -0,0 +1,65 @@
+// Copyright (C) 2021 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+
+#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 {
+
+class QMLDOM_EXPORT FileWriter
+{
+ Q_GADGET
+ Q_DECLARE_TR_FUNCTIONS(FileWriter)
+public:
+ enum class Status { ShouldWrite, DidWrite, SkippedEqual, SkippedDueToFailure };
+
+ FileWriter() = default;
+
+ ~FileWriter()
+ {
+ if (!silentWarnings) {
+ for (const QString &w : std::as_const(warnings))
+ qWarning() << w;
+ }
+ if (shouldRemoveTempFile)
+ tempFile.remove();
+ }
+
+ Status write(const 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/qqmldomfunctionref_p.h b/src/qmldom/qqmldomfunctionref_p.h
new file mode 100644
index 0000000000..525178e841
--- /dev/null
+++ b/src/qmldom/qqmldomfunctionref_p.h
@@ -0,0 +1,60 @@
+// Copyright (C) 2021 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+
+#ifndef QQMLDOMFUNCTIONREF_P_H
+#define QQMLDOMFUNCTIONREF_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 <QtCore/private/qglobal_p.h>
+
+#if !defined(Q_CC_MSVC) || Q_CC_MSVC >= 1930
+#include <QtCore/qxpfunctional.h>
+
+QT_BEGIN_NAMESPACE
+namespace QQmlJS {
+namespace Dom {
+template <typename T>
+using function_ref = qxp::function_ref<T>;
+} // namespace Dom
+} // namespace QQmlJS
+QT_END_NAMESPACE
+
+#else
+
+#include <functional>
+
+QT_BEGIN_NAMESPACE
+namespace QQmlJS {
+namespace Dom {
+namespace _detail {
+template <typename T>
+struct function_ref_helper { using type = std::function<T>; };
+// std::function doesn't grok the const in <int(int) const>, so remove:
+template <typename R, typename...Args>
+struct function_ref_helper<R(Args...) const> : function_ref_helper<R(Args...)> {};
+// std::function doesn't grok the noexcept in <int(int) noexcept>, so remove:
+template <typename R, typename...Args>
+struct function_ref_helper<R(Args...) noexcept> : function_ref_helper<R(Args...)> {};
+// and both together:
+template <typename R, typename...Args>
+struct function_ref_helper<R(Args...) const noexcept> : function_ref_helper<R(Args...)> {};
+} // namespace _detail
+template <typename T>
+using function_ref = const typename _detail::function_ref_helper<T>::type &;
+} // namespace Dom
+} // namespace QQmlJS
+QT_END_NAMESPACE
+
+#endif
+
+#endif // QQMLDOMFUNCTIONREF_P_H
diff --git a/src/qmldom/qqmldomindentinglinewriter.cpp b/src/qmldom/qqmldomindentinglinewriter.cpp
new file mode 100644
index 0000000000..99f81c1717
--- /dev/null
+++ b/src/qmldom/qqmldomindentinglinewriter.cpp
@@ -0,0 +1,126 @@
+// Copyright (C) 2022 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+
+#include "qqmldomindentinglinewriter_p.h"
+
+#include <QtCore/QCoreApplication>
+#include <QtCore/QRegularExpression>
+
+QT_BEGIN_NAMESPACE
+namespace QQmlJS {
+namespace Dom {
+
+FormatPartialStatus &IndentingLineWriter::fStatus()
+{
+ if (!m_fStatusValid) {
+ m_fStatus = formatCodeLine(m_currentLine, m_options.formatOptions, m_preCachedStatus);
+ m_fStatusValid = true;
+ }
+ return m_fStatus;
+}
+
+void IndentingLineWriter::willCommit()
+{
+ m_preCachedStatus = fStatus().currentStatus;
+}
+
+void IndentingLineWriter::reindentAndSplit(const QString &eol, bool eof)
+{
+ bool shouldReindent = m_reindent;
+indentAgain:
+ // maybe re-indent
+ if (shouldReindent && m_columnNr == 0) {
+ setLineIndent(fStatus().indentLine());
+ }
+ if (!eol.isEmpty() || eof) {
+ LineWriterOptions::TrailingSpace trailingSpace;
+ if (!m_currentLine.isEmpty() && m_currentLine.trimmed().isEmpty()) {
+ // space only line
+ const Scanner::State &oldState = m_preCachedStatus.lexerState;
+ if (oldState.isMultilineComment())
+ trailingSpace = m_options.commentTrailingSpace;
+ else if (oldState.isMultiline())
+ trailingSpace = m_options.stringTrailingSpace;
+ else
+ trailingSpace = m_options.codeTrailingSpace;
+ // in the LSP we will probably want to treat is specially if it is the line with the
+ // cursor, of if indentation of it is requested
+ } else {
+ const Scanner::State &currentState = fStatus().currentStatus.lexerState;
+ if (currentState.isMultilineComment()) {
+ trailingSpace = m_options.commentTrailingSpace;
+ } else if (currentState.isMultiline()) {
+ trailingSpace = m_options.stringTrailingSpace;
+ } else {
+ const int kind =
+ (fStatus().lineTokens.isEmpty() ? Lexer::T_EOL
+ : fStatus().lineTokens.last().lexKind);
+ if (Token::lexKindIsComment(kind)) {
+ // a // comment...
+ trailingSpace = m_options.commentTrailingSpace;
+ Q_ASSERT(fStatus().currentStatus.state().type
+ != FormatTextStatus::StateType::MultilineCommentCont
+ && fStatus().currentStatus.state().type
+ != FormatTextStatus::StateType::
+ MultilineCommentStart); // these should have been
+ // handled above
+ } else {
+ trailingSpace = m_options.codeTrailingSpace;
+ }
+ }
+ }
+ handleTrailingSpace(trailingSpace);
+ }
+ // maybe split long line
+ if (m_options.maxLineLength > 0 && m_currentLine.size() > m_options.maxLineLength) {
+ int possibleSplit = -1;
+ if (fStatus().lineTokens.size() > 1) {
+ // {}[] should already be handled (handle also here?)
+ int minLen = 0;
+ while (minLen < m_currentLine.size() && m_currentLine.at(minLen).isSpace())
+ ++minLen;
+ minLen = column(minLen) + m_options.minContentLength;
+ int maxLen = qMax(minLen + m_options.strongMaxLineExtra, m_options.maxLineLength);
+ std::array<QSet<int>, 2> splitSequence(
+ { QSet<int>({ // try split after ',','||','&&'
+ QQmlJSGrammar::T_COMMA, QQmlJSGrammar::T_AND_AND,
+ QQmlJSGrammar::T_OR_OR }),
+ QSet<int>({ // try split after '('
+ QQmlJSGrammar::T_LPAREN }) });
+ // try split after other binary operators?
+ int minSplit = m_currentLine.size();
+ for (const QSet<int> &splitOnToken : splitSequence) {
+ for (int iToken = 0; iToken < fStatus().tokenCount(); ++iToken) {
+ const Token t = fStatus().tokenAt(iToken);
+ int tCol = column(t.end());
+ if (splitOnToken.contains(t.lexKind) && tCol > minLen) {
+ if (tCol <= maxLen && possibleSplit < t.end())
+ possibleSplit = t.end();
+ if (t.end() < minSplit)
+ minSplit = t.end();
+ }
+ }
+ if (possibleSplit > 0)
+ break;
+ }
+ if (possibleSplit == -1 && minSplit + 4 < m_currentLine.size())
+ possibleSplit = minSplit;
+ if (possibleSplit > 0) {
+ lineChanged();
+ quint32 oChange = eolToWrite().size();
+ changeAtOffset(m_utf16Offset + possibleSplit, oChange, 0,
+ 0); // line & col change updated in commitLine
+ commitLine(eolToWrite(), TextAddType::NewlineSplit, possibleSplit);
+ shouldReindent = true;
+ goto indentAgain;
+ }
+ }
+ }
+ // maybe write out
+ if (!eol.isEmpty() || eof)
+ commitLine(eol);
+}
+
+} // namespace Dom
+} // namespace QQmlJS
+QT_END_NAMESPACE
diff --git a/src/qmldom/qqmldomindentinglinewriter_p.h b/src/qmldom/qqmldomindentinglinewriter_p.h
new file mode 100644
index 0000000000..63ec3fc432
--- /dev/null
+++ b/src/qmldom/qqmldomindentinglinewriter_p.h
@@ -0,0 +1,62 @@
+// Copyright (C) 2022 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+
+#ifndef QQMLDOMINDENTIGLINEWRITER_P
+#define QQMLDOMINDENTIGLINEWRITER_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 "qqmldomcodeformatter_p.h"
+#include "qqmldomlinewriter_p.h"
+
+#include <QtQml/private/qqmljssourcelocation_p.h>
+#include <QtCore/QAtomicInt>
+#include <functional>
+
+QT_BEGIN_NAMESPACE
+namespace QQmlJS {
+namespace Dom {
+
+QMLDOM_EXPORT class IndentingLineWriter : public LineWriter
+{
+ Q_GADGET
+public:
+ IndentingLineWriter(const SinkF &innerSink, const QString &fileName,
+ const LineWriterOptions &options = LineWriterOptions(),
+ const FormatTextStatus &initialStatus = FormatTextStatus::initialStatus(),
+ int lineNr = 0, int columnNr = 0, int utf16Offset = 0,
+ QString currentLine = QString())
+ : LineWriter(innerSink, fileName, options, lineNr, columnNr, utf16Offset, currentLine),
+ m_preCachedStatus(initialStatus)
+ {
+ }
+ void reindentAndSplit(const QString &eol, bool eof = false) override;
+ FormatPartialStatus &fStatus();
+
+ void lineChanged() override { m_fStatusValid = false; }
+ void willCommit() override;
+ bool reindent() const { return m_reindent; }
+ void setReindent(bool v) { m_reindent = v; }
+
+private:
+ Q_DISABLE_COPY_MOVE(IndentingLineWriter)
+protected:
+ FormatTextStatus m_preCachedStatus;
+ bool m_fStatusValid = false;
+ FormatPartialStatus m_fStatus;
+};
+
+} // namespace Dom
+} // namespace QQmlJS
+QT_END_NAMESPACE
+#endif
diff --git a/src/qmldom/qqmldomitem.cpp b/src/qmldom/qqmldomitem.cpp
index 296083c99b..b885422c1d 100644
--- a/src/qmldom/qqmldomitem.cpp
+++ b/src/qmldom/qqmldomitem.cpp
@@ -1,42 +1,22 @@
-/****************************************************************************
-**
-** 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$
-**/
+// Copyright (C) 2020 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+#include "qqmldomattachedinfo_p.h"
+#include "qqmldomconstants_p.h"
#include "qqmldomitem_p.h"
+#include "qqmldompath_p.h"
#include "qqmldomtop_p.h"
+#include "qqmldomelements_p.h"
+#include "qqmldomexternalitems_p.h"
+#include "qqmldommock_p.h"
+#include "qqmldomastdumper_p.h"
+#include "qqmldomoutwriter_p.h"
+#include "qqmldomfilewriter_p.h"
+#include "qqmldomfieldfilter_p.h"
+#include "qqmldomcompare_p.h"
+#include "qqmldomastdumper_p.h"
+#include "qqmldomlinewriter_p.h"
+#include "qqmldom_utils_p.h"
+#include "qqmldomscriptelements_p.h"
#include <QtQml/private/qqmljslexer_p.h>
#include <QtQml/private/qqmljsparser_p.h>
@@ -44,23 +24,62 @@
#include <QtQml/private/qqmljsastvisitor_p.h>
#include <QtQml/private/qqmljsast_p.h>
+#include <QtCore/QCborArray>
+#include <QtCore/QCborMap>
#include <QtCore/QDebug>
+#include <QtCore/QDir>
#include <QtCore/QFile>
#include <QtCore/QFileInfo>
+#include <QtCore/QJsonDocument>
+#include <QtCore/QJsonValue>
+#include <QtCore/QMutexLocker>
#include <QtCore/QPair>
+#include <QtCore/QRegularExpression>
#include <QtCore/QScopeGuard>
-#include <QtCore/QMutexLocker>
-#include <QtCore/QCborMap>
-#include <QtCore/QCborArray>
-#include <QtCore/QJsonValue>
-#include <QtCore/QJsonDocument>
+#include <QtCore/QtGlobal>
+#include <QtCore/QTimeZone>
+#include <optional>
+#include <type_traits>
QT_BEGIN_NAMESPACE
-
namespace QQmlJS {
namespace Dom {
+Q_LOGGING_CATEGORY(writeOutLog, "qt.qmldom.writeOut", QtWarningMsg);
+static Q_LOGGING_CATEGORY(refLog, "qt.qmldom.ref", QtWarningMsg);
+
+template<class... TypeList>
+struct CheckDomElementT;
+
+template<class... Ts>
+struct CheckDomElementT<std::variant<Ts...>> : std::conjunction<IsInlineDom<Ts>...>
+{
+};
+
+/*!
+ \internal
+ \class QQmljs::Dom::ElementT
+
+ \brief A variant that contains all the Dom elements that an DomItem can contain.
+
+ Types in this variant are divided in two categories: normal Dom elements and internal Dom
+ elements.
+ The first ones are inheriting directly or indirectly from DomBase, and are the usual elements
+ that a DomItem can wrap around, like a QmlFile or an QmlObject. They should all appear in
+ ElementT as pointers, e.g. QmlFile*.
+ The internal Dom elements are a little bit special. They appear in ElementT without pointer, do
+ not inherit from DomBase \b{but} should behave like a smart DomBase-pointer. That is, they should
+ dereference as if they were a DomBase* pointing to a normal DomElement by implementing
+ operator->() and operator*().
+ Adding types here that are neither inheriting from DomBase nor implementing a smartpointer to
+ DomBase will throw compilation errors in the std::visit()-calls on this type.
+*/
+static_assert(CheckDomElementT<ElementT>::value,
+ "Types in ElementT must either be a pointer to a class inheriting "
+ "from DomBase or (for internal Dom structures) implement a smart "
+ "pointer pointing to a class inheriting from DomBase");
+
using std::shared_ptr;
/*!
\internal
@@ -79,17 +98,36 @@ The subclass *must* have a
\endcode
entry with its kind to enable casting usng the DomItem::as DomItem::ownerAs templates.
-The minimal overload set to be usable is:
+The minimal overload set to be usable consists of following methods:
+\list
+\li \c{kind()} returns the kind of the current element:
+\code
+ Kind kind() const override { return kindValue; }
+\endcode
+
+\li \c{pathFromOwner()} returns the path from the owner to the current element
+\code
+ Path pathFromOwner(const DomItem &self) const override;
+\endcode
+
+\li \c{canonicalPath()} returns the path
+\code
+ Path canonicalPath(const DomItem &self) const override;
+\endcode
+
+\li \c{iterateDirectSubpaths} iterates the *direct* subpaths/children and returns false if a quick
+end was requested:
\code
- Kind kind() const override { return kindValue; } // returns the kind of the current element
- Path pathFromOwner(const DomItem &self) const override; // returns the path from the owner to the current element
- Path canonicalPath(const DomItem &self) const override; // returns the path from
- virtual bool iterateDirectSubpaths(const DomItem &self, std::function<bool(Path, DomItem)>) const = 0; // iterates the *direct* subpaths, returns false if a quick end was requested
+bool iterateDirectSubpaths(const DomItem &self, function_ref<bool(Path, DomItem)>) const = 0;
\endcode
-But you probably want to subclass either DomElement of OwningItem for your element.
-DomElement stores its pathFromOwner, and computes the canonicalPath from it and its owner.
-OwningItem is the unit for updates to the Dom model, exposed changes always change at least one OwningItem.
-They have their lifetime handled with shared_ptr and own (i.e. are responsible of freeing) other items in them.
+
+\endlist
+
+But you probably want to subclass either \c DomElement or \c OwningItem for your element. \c
+DomElement stores its \c pathFromOwner, and computes the \c canonicalPath from it and its owner. \c
+OwningItem is the unit for updates to the Dom model, exposed changes always change at least one \c
+OwningItem. They have their lifetime handled with \c shared_ptr and own (i.e. are responsible of
+freeing) other items in them.
\sa QQml::Dom::DomItem, QQml::Dom::DomElement, QQml::Dom::OwningItem
*/
@@ -109,78 +147,31 @@ QMap<DomType,QString> domTypeToStringMap()
QString domTypeToString(DomType k)
{
- return domTypeToStringMap().value(k, QString::number(int(k)));
+ QString res = domTypeToStringMap().value(k);
+ if (res.isEmpty())
+ return QString::number(int(k));
+ else
+ return res;
}
-bool domTypeIsObjWrap(DomType k)
+QMap<DomKind, QString> domKindToStringMap()
{
- switch (k) {
- case DomType::ModuleAutoExport:
- case DomType::Import:
- case DomType::Export:
- case DomType::SimpleObjectWrap:
- case DomType::Version:
- case DomType::ErrorMessage:
- case DomType::PropertyDefinition:
- case DomType::MethodParameter:
- case DomType::MethodInfo:
- case DomType::Pragma:
- case DomType::Id:
- case DomType::EnumItem:
- case DomType::Binding:
- case DomType::RequiredProperty:
- return true;
- default:
- return false;
- }
+ static QMap<DomKind, QString> map = []() {
+ QMetaEnum metaEnum = QMetaEnum::fromType<DomKind>();
+ QMap<DomKind, QString> res;
+ for (int i = 0; i < metaEnum.keyCount(); ++i) {
+ res[DomKind(metaEnum.value(i))] = QString::fromUtf8(metaEnum.key(i));
+ }
+ return res;
+ }();
+ return map;
}
-bool domTypeIsDomElement(DomType k)
+QString domKindToString(DomKind k)
{
- switch (k) {
- case DomType::ModuleScope:
- case DomType::QmlObject:
- case DomType::ConstantData:
- case DomType::SimpleObjectWrap:
- case DomType::ScriptExpression:
- case DomType::Reference:
- case DomType::Map:
- case DomType::List:
- case DomType::EnumDecl:
- case DomType::JsResource:
- case DomType::QmltypesComponent:
- case DomType::QmlComponent:
- case DomType::GlobalComponent:
- case DomType::GenericObject:
- return true;
- default:
- return false;
- }
+ return domKindToStringMap().value(k, QString::number(int(k)));
}
-bool domTypeIsOwningItem(DomType k)
-{
- switch (k) {
- case DomType::ModuleIndex:
-
- case DomType::GenericOwner:
-
- case DomType::QmlDirectory:
- case DomType::JsFile:
- case DomType::QmlFile:
- case DomType::QmltypesFile:
- case DomType::GlobalScope:
-
- case DomType::LoadInfo:
-
- case DomType::DomEnvironment:
- case DomType::DomUniverse:
- return true;
- default:
- return false;
- }
-};
-
bool domTypeIsExternalItem(DomType k)
{
switch (k) {
@@ -212,139 +203,32 @@ bool domTypeIsContainer(DomType k)
switch (k) {
case DomType::Map:
case DomType::List:
+ case DomType::ListP:
return true;
default:
return false;
}
}
-bool domTypeCanBeInline(DomType k)
+bool domTypeIsScope(DomType k)
{
switch (k) {
- case DomType::Empty:
- case DomType::Map:
- case DomType::List:
- case DomType::ConstantData:
- case DomType::SimpleObjectWrap:
- case DomType::Reference:
+ case DomType::QmlObject: // prop, methods,...
+ case DomType::ScriptExpression: // Js lexical scope
+ case DomType::QmlComponent: // (ids, enums -> qmlObj)
+ case DomType::QmlFile: // (components ->importScope)
+ case DomType::MethodInfo: // method arguments
+ case DomType::ImportScope: // (types, qualifiedImports)
+ case DomType::GlobalComponent: // global scope (enums -> qmlObj)
+ case DomType::JsResource: // js resurce (enums -> qmlObj)
+ case DomType::QmltypesComponent: // qmltypes component (enums -> qmlObj)
return true;
default:
return false;
}
-};
-
-DomKind DomBase::domKind() const
-{
- return kind2domKind(kind());
-}
-
-bool DomBase::iterateDirectSubpathsConst(const DomItem &self, std::function<bool (Path, const DomItem &)>visitor) const
-{
- return const_cast<DomBase *>(this)->iterateDirectSubpaths(
- *const_cast<DomItem *>(&self),
- [visitor](Path p, DomItem &item) {
- return visitor(p, item);
- });
-}
-
-DomItem DomBase::containingObject(const DomItem &self) const
-{
- Path path = pathFromOwner(self);
- DomItem base = self.owner();
- if (!path) {
- path = canonicalPath(self);
- base = self;
- }
- Source source = path.split();
- return base.path(source.pathToSource);
-}
-
-quintptr DomBase::id() const
-{
- return quintptr(this);
-}
-
-QString DomBase::typeName() const
-{
- return domTypeToStringMap()[kind()];
-}
-
-QList<QString> const DomBase::fields(const DomItem &self) const
-{
- QList<QString> res;
- iterateDirectSubpathsConst(self, [&res](Path p, const DomItem &){
- if (p.headKind() == Path::Kind::Field)
- res.append(p.headName());
- return true;
- });
- return res;
}
-DomItem DomBase::field(const DomItem &self, QStringView name) const
-{
- DomItem res;
- iterateDirectSubpathsConst(self, [&res, name](Path p, const DomItem & i){
- if (p.headKind() == Path::Kind::Field && p.checkHeadName(name)) {
- res = i;
- return false;
- }
- return true;
- });
- return res;
-}
-
-index_type DomBase::indexes(const DomItem &self) const
-{
- index_type res = 0;
- iterateDirectSubpathsConst(self, [&res](Path p, const DomItem &){
- if (p.headKind() == Path::Kind::Index) {
- index_type i = p.headIndex() + 1;
- if (res < i)
- res = i;
- }
- return true;
- });
- return res;
-}
-
-DomItem DomBase::index(const DomItem &self, qint64 index) const
-{
- DomItem res;
- iterateDirectSubpathsConst(self, [&res, index](Path p, const DomItem &i){
- if (p.headKind() == Path::Kind::Index && p.headIndex() == index) {
- res = i;
- return false;
- }
- return true;
- });
- return res;
-}
-
-QSet<QString> const DomBase::keys(const DomItem &self) const
-{
- QSet<QString> res;
- iterateDirectSubpathsConst(self, [&res](Path p, const DomItem &){
- if (p.headKind() == Path::Kind::Key)
- res.insert(p.headName());
- return true;
- });
- return res;
-}
-
-DomItem DomBase::key(const DomItem &self, QString name) const
-{
- DomItem res;
- iterateDirectSubpathsConst(self, [&res, name](Path p, const DomItem &i){
- if (p.headKind() == Path::Kind::Key && p.checkHeadName(name)) {
- res = i;
- return false;
- }
- return true;
- });
- return res;
-}
-
-QString DomBase::canonicalFilePath(const DomItem & self) const
+QString DomBase::canonicalFilePath(const DomItem &self) const
{
auto parent = containingObject(self);
if (parent)
@@ -352,36 +236,43 @@ QString DomBase::canonicalFilePath(const DomItem & self) const
return QString();
}
-SourceLocation DomBase::location(const DomItem & self) const
+void DomBase::writeOut(const DomItem &self, OutWriter &) const
{
- auto parent = containingObject(self);
- if (parent)
- return parent.location();
- return SourceLocation();
+ qCWarning(writeOutLog) << "Ignoring unsupported writeOut for " << domTypeToString(kind()) << ":"
+ << self.canonicalPath();
}
-ConstantData::ConstantData(Path pathFromOwner, QCborValue value, Options options, const SourceLocation & loc):
- DomElement(pathFromOwner, loc), m_value(value), m_options(options)
+ConstantData::ConstantData(const Path &pathFromOwner, const QCborValue &value, Options options)
+ : DomElement(pathFromOwner), m_value(value), m_options(options)
{}
-bool ConstantData::iterateDirectSubpaths(DomItem &self, function<bool (Path, DomItem &)> visitor)
+bool ConstantData::iterateDirectSubpaths(const DomItem &self, DirectVisitor visitor) const
{
+ static QHash<QString, QString> knownFields;
+ static QBasicMutex m;
+ auto toField = [](const QString &f) -> QStringView {
+ QMutexLocker l(&m);
+ if (!knownFields.contains(f))
+ knownFields[f] = f;
+ return knownFields[f];
+ };
if (m_value.isMap()) {
QCborMap map = m_value.toMap();
auto it = map.cbegin();
auto end = map.cend();
while (it != end) {
QString key = it.key().toString();
- Path path;
+ PathEls::PathComponent comp;
switch (m_options) {
case ConstantData::Options::MapIsMap:
- path = Path::key(key);
+ comp = PathEls::Key(key);
break;
case ConstantData::Options::FirstMapIsFields:
- path = Path::field(key);
+ comp = PathEls::Field(toField(key));
break;
}
- if (!self.subDataPath(path, it.value()).visit(visitor))
+ auto val = it.value();
+ if (!self.dvValue(visitor, comp, val))
return false;
++it;
}
@@ -392,7 +283,7 @@ bool ConstantData::iterateDirectSubpaths(DomItem &self, function<bool (Path, Dom
auto end = array.cend();
index_type i = 0;
while (it != end) {
- if (!self.subDataPath(Path::index(i++), *it++).visit(visitor))
+ if (!self.dvValue(visitor, PathEls::Index(i++), *it++))
return false;
}
return true;
@@ -420,27 +311,6 @@ DomKind ConstantData::domKind() const
return DomKind::List;
return DomKind::Value;
}
-
-SimpleObjectWrap::SimpleObjectWrap(Path pathFromOwner, QVariant value,
- std::function<bool(DomItem &, QVariant, std::function<bool(Path, DomItem &)>)> directSubpathsIterate,
- DomType kind,
- DomKind domKind,
- QString typeName,
- const SourceLocation & loc):
- DomElement(pathFromOwner, loc), m_kind(kind), m_domKind(domKind), m_typeName(typeName), m_value(value),
- m_directSubpathsIterate(directSubpathsIterate)
-{}
-
-bool SimpleObjectWrap::iterateDirectSubpaths(DomItem &self, function<bool (Path, DomItem &)> visitor)
-{
- return m_directSubpathsIterate(self, m_value, visitor);
-}
-
-quintptr SimpleObjectWrap::id() const
-{
- return quintptr(m_value.value<void *>());
-}
-
/*!
\internal
\class QQmlJS::Dom::DomItem
@@ -457,15 +327,14 @@ and to the DomEnvironment or DomUniverse that contains them. This means that:
containingObject() and container().
\li the indexing operator [], or the path(), field(), key() and index() methods (along with their
fields(), keys(), and indexes() contreparts) let one visit the contents of the current element.
-\li visitChildren can be used to visit all subEments, if preferred on the top of it a visitor
+\li visitTree can be used to visit all subEments, if preferred on the top of it a visitor
pattern can also be used.
-\li If element specific attributes are wanted the two template casting as and ownerAs allow safe casting
-of the DomItem to a specific concrete type (cast to superclasses is not supported).
-\li Multithreading does not create issues, because even if an update replacing an OwningItem takes
- place the DomItem keeps a shared_ptr to the current owner as long as you use it
-\li Some elements (Empty, List, Map, ConstantData, Reference) might be inline, meaning that they are
- generated on the fly, wrapping data of the original object.
-\endlist
+\li If element specific attributes are wanted the two template casting as and ownerAs allow safe
+casting of the DomItem to a specific concrete type (cast to superclasses is not supported). \li
+Multithreading does not create issues, because even if an update replacing an OwningItem takes place
+the DomItem keeps a shared_ptr to the current owner as long as you use it \li Some elements (Empty,
+List, Map, ConstantData, Reference) might be inline, meaning that they are generated on the fly,
+wrapping data of the original object. \endlist
One of the goals of the DomItem is to allow one to use real typed objects, as one is used to in C++,
and also let one use modern C++ patterns, meaning container that contain the actual object (without
@@ -478,7 +347,43 @@ It does not keep any pointers to internal elements, but rather the path to them,
it every time it needs.
*/
+FileToLoad::FileToLoad(const std::weak_ptr<DomEnvironment> &environment,
+ const QString &canonicalPath, const QString &logicalPath,
+ const std::optional<InMemoryContents> &content)
+ : m_environment(environment),
+ m_canonicalPath(canonicalPath),
+ m_logicalPath(logicalPath),
+ m_content(content)
+{
+}
+
+FileToLoad FileToLoad::fromMemory(const std::weak_ptr<DomEnvironment> &environment,
+ const QString &path, const QString &code)
+{
+ const QString canonicalPath = QFileInfo(path).canonicalFilePath();
+ return {
+ environment,
+ canonicalPath,
+ path,
+ InMemoryContents{ code },
+ };
+}
+
+FileToLoad FileToLoad::fromFileSystem(const std::weak_ptr<DomEnvironment> &environment,
+ const QString &path)
+{
+ // make the path canonical so the file content can be loaded from it later
+ const QString canonicalPath = QFileInfo(path).canonicalFilePath();
+ return {
+ environment,
+ canonicalPath,
+ path,
+ std::nullopt,
+ };
+}
+
ErrorGroup DomItem::domErrorGroup = NewErrorGroup("Dom");
+DomItem DomItem::empty = DomItem();
ErrorGroups DomItem::myErrors()
{
@@ -494,15 +399,64 @@ ErrorGroups DomItem::myResolveErrors()
Path DomItem::canonicalPath() const
{
- Path res = base()->canonicalPath(*this);
- Q_ASSERT((!res || res.headKind() == Path::Kind::Root) && "non anchored canonical path");
+ Path res = visitEl([this](auto &&el) { return el->canonicalPath(*this); });
+ if (!(!res || res.headKind() == Path::Kind::Root)) {
+ qCWarning(domLog) << "non anchored canonical path:" << res.toString();
+ Q_ASSERT(false);
+ }
return res;
}
DomItem DomItem::containingObject() const
{
- return base()->containingObject(*this);
+ return visitEl([this](auto &&el) { return el->containingObject(*this); });
+}
+
+/*!
+ \internal
+ \brief Returns the QmlObject that this belongs to.
+
+ qmlObject() might also return the object of a component if GoTo:MostLikely is used.
+ */
+DomItem DomItem::qmlObject(GoTo options, FilterUpOptions filterOptions) const
+{
+ if (DomItem res = filterUp([](DomType k, const DomItem &) { return k == DomType::QmlObject; },
+ filterOptions))
+ return res;
+ if (options == GoTo::MostLikely) {
+ if (DomItem comp = component(options))
+ return comp.field(Fields::objects).index(0);
+ }
+ return DomItem();
+}
+
+DomItem DomItem::fileObject(GoTo options) const
+{
+ DomItem res = *this;
+ DomType k = res.internalKind();
+ if (k == DomType::List || k == DomType::Map) {
+ res = res.containingObject();
+ k = res.internalKind();
+ }
+ if (k == DomType::ExternalItemInfo || (options == GoTo::MostLikely && k == DomType::ExternalItemPair))
+ return field(Fields::currentItem);
+ res = owner();
+ k = res.internalKind();
+ while (k != DomType::Empty) {
+ if (k == DomType::QmlFile || k == DomType::QmldirFile || k == DomType::QmltypesFile
+ || k == DomType::JsFile)
+ break;
+ res = res.containingObject();
+ res = res.owner();
+ k = res.internalKind();
+ }
+ return res;
+}
+
+DomItem DomItem::rootQmlObject(GoTo options) const
+{
+ return qmlObject(options, FilterUpOptions::ReturnInner);
}
DomItem DomItem::container() const
@@ -516,13 +470,44 @@ DomItem DomItem::container() const
return containingObject();
}
-DomItem DomItem::owner() const {
- return DomItem(m_top, m_owner, m_owner.get());
+DomItem DomItem::globalScope() const
+{
+ if (internalKind() == DomType::GlobalScope)
+ return *this;
+ DomItem env = environment();
+ if (shared_ptr<DomEnvironment> envPtr = env.ownerAs<DomEnvironment>()) {
+ return env.copy(envPtr->ensureGlobalScopeWithName(env, envPtr->globalScopeName())->current,
+ Path());
+ }
+ return DomItem();
+}
+
+/*!
+ \internal
+ \brief The owner of an element, for an qmlObject this is the containing qml file.
+ */
+DomItem DomItem::owner() const
+{
+ if (domTypeIsOwningItem(m_kind) || m_kind == DomType::Empty)
+ return *this;
+ return std::visit([this](auto &&el) {
+ if constexpr (std::is_same_v<std::decay_t<decltype(el)>, std::monostate>)
+ return DomItem();
+ else
+ return DomItem(this->m_top, el, this->m_ownerPath, el.get());
+ }, m_owner);
}
DomItem DomItem::top() const
{
- return DomItem(m_top, m_top, m_top.get());
+ if (domTypeIsTopItem(m_kind) || m_kind == DomType::Empty)
+ return *this;
+ return std::visit([](auto &&el) {
+ if constexpr (std::is_same_v<std::decay_t<decltype(el)>, std::monostate>)
+ return DomItem();
+ else
+ return DomItem(el, el, Path(), el.get());
+ }, m_top);
}
DomItem DomItem::environment() const
@@ -543,29 +528,222 @@ DomItem DomItem::universe() const
return DomItem(); // we should be in an empty DomItem already...
}
-QString DomItem::name() const
+/*!
+ \internal
+ Shorthand to obtain the ScriptExpression DomItem, in which this DomItem is defined.
+ Returns an empty DomItem if the item is not defined inside a ScriptExpression.
+ \sa goToFile()
+ */
+DomItem DomItem::containingScriptExpression() const
+{
+ if (DomItem res = filterUp([](DomType k, const DomItem &) { return k == DomType::ScriptExpression; },
+ FilterUpOptions::ReturnOuter))
+ return res;
+ return DomItem();
+}
+
+/*!
+ \internal
+ Shorthand to obtain the QmlFile DomItem, in which this DomItem is defined.
+ Returns an empty DomItem if the item is not defined in a QML file.
+ \sa goToFile()
+ */
+DomItem DomItem::containingFile() const
+{
+ if (DomItem res = filterUp([](DomType k, const DomItem &) { return k == DomType::QmlFile; },
+ FilterUpOptions::ReturnOuter))
+ return res;
+ return DomItem();
+}
+
+/*!
+ \internal
+ Shorthand to obtain the QmlFile DomItem from a canonicalPath.
+ \sa containingFile()
+ */
+DomItem DomItem::goToFile(const QString &canonicalPath) const
{
- return field(Fields::name).value().toString();
+ Q_UNUSED(canonicalPath);
+ DomItem file =
+ top().field(Fields::qmlFileWithPath).key(canonicalPath).field(Fields::currentItem);
+ return file;
+}
+
+/*!
+ \internal
+ In the DomItem hierarchy, go \c n levels up.
+ */
+DomItem DomItem::goUp(int n) const
+{
+ Path path = canonicalPath();
+ // first entry of path is usually top(), and you cannot go up from top().
+ if (path.length() < n + 1)
+ return DomItem();
+
+ DomItem parent = top().path(path.dropTail(n));
+ return parent;
}
-DomItem DomItem::qmlChildren() const
+/*!
+ \internal
+ In the DomItem hierarchy, go 1 level up to get the direct parent.
+ */
+DomItem DomItem::directParent() const
{
- return field(Fields::children);
+ return goUp(1);
}
-DomItem DomItem::annotations() const
+/*!
+\internal
+Finds the first element in the DomItem hierarchy that satisfies filter.
+Use options to set the search direction, see also \l{FilterUpOptions}.
+*/
+DomItem DomItem::filterUp(function_ref<bool(DomType k, const DomItem &)> filter, FilterUpOptions options) const
{
- return field(Fields::annotations);
+ if (options == FilterUpOptions::ReturnOuter && filter(internalKind(), *this)) {
+ return *this;
+ }
+
+ switch (options) {
+ case FilterUpOptions::ReturnOuter:
+ case FilterUpOptions::ReturnOuterNoSelf: {
+ for (DomItem current = *this, previous = DomItem(); current;
+ previous = current, current = current.directParent()) {
+ if (filter(current.internalKind(), current)) {
+ if (options != FilterUpOptions::ReturnOuterNoSelf || current != *this)
+ return current;
+ }
+ }
+ break;
+ }
+ case FilterUpOptions::ReturnInner:
+ DomItem current = top();
+ for (const Path &currentPath : canonicalPath()) {
+ current = current.path(currentPath);
+ if (filter(current.internalKind(), current))
+ return current;
+ }
+ break;
+ }
+
+ return DomItem();
+}
+
+DomItem DomItem::scope(FilterUpOptions options) const
+{
+ DomItem res = filterUp([](DomType, const DomItem &el) { return el.isScope(); }, options);
+ return res;
+}
+
+QQmlJSScope::ConstPtr DomItem::nearestSemanticScope() const
+{
+ QQmlJSScope::ConstPtr scope;
+ visitUp([&scope](const DomItem &item) {
+ scope = item.semanticScope();
+ return !scope; // stop when scope was true
+ });
+ return scope;
+}
+
+QQmlJSScope::ConstPtr DomItem::semanticScope() const
+{
+ QQmlJSScope::ConstPtr scope = std::visit(
+ [](auto &&e) -> QQmlJSScope::ConstPtr {
+ using T = std::remove_cv_t<std::remove_reference_t<decltype(e)>>;
+ if constexpr (std::is_same_v<T, const QmlObject *>) {
+ return e->semanticScope();
+ } else if constexpr (std::is_same_v<T, const QmlComponent *>) {
+ return e->semanticScope();
+ } else if constexpr (std::is_same_v<T, const QmltypesComponent *>) {
+ return e->semanticScope();
+ } else if constexpr (std::is_same_v<T, SimpleObjectWrap>) {
+ if (const MethodInfo *mi = e->template as<MethodInfo>()) {
+ return mi->semanticScope();
+ }
+ if (const auto *propertyDefinition = e->template as<PropertyDefinition>()) {
+ return propertyDefinition->semanticScope();
+ }
+ } else if constexpr (std::is_same_v<T, ScriptElementDomWrapper>) {
+ return e.element().base()->semanticScope();
+ }
+ return {};
+ },
+ m_element);
+ return scope;
+}
+
+DomItem DomItem::get(const ErrorHandler &h, QList<Path> *visitedRefs) const
+{
+ if (const Reference *refPtr = as<Reference>())
+ return refPtr->get(*this, h, visitedRefs);
+ return DomItem();
+}
+
+QList<DomItem> DomItem::getAll(const ErrorHandler &h, QList<Path> *visitedRefs) const
+{
+ if (const Reference *refPtr = as<Reference>())
+ return refPtr->getAll(*this, h, visitedRefs);
+ return {};
}
-DomItem DomItem::component() const
+PropertyInfo DomItem::propertyInfoWithName(const QString &name) const
{
- DomItem item = *this;
- while (item) {
- DomType kind = item.internalKind() ;
- if (kind == DomType::QmlComponent || kind == DomType::QmltypesComponent || kind == DomType::GlobalComponent)
- return item;
- item = item.containingObject();
+ PropertyInfo pInfo;
+ visitPrototypeChain([&pInfo, name](const DomItem &obj) {
+ return obj.visitLocalSymbolsNamed(name, [&pInfo, name](const DomItem &el) {
+ switch (el.internalKind()) {
+ case DomType::Binding:
+ pInfo.bindings.append(el);
+ break;
+ case DomType::PropertyDefinition:
+ pInfo.propertyDefs.append(el);
+ break;
+ default:
+ break;
+ }
+ return true;
+ });
+ });
+ return pInfo;
+}
+
+QSet<QString> DomItem::propertyInfoNames() const
+{
+ QSet<QString> res;
+ visitPrototypeChain([&res](const DomItem &obj) {
+ res += obj.propertyDefs().keys();
+ res += obj.bindings().keys();
+ return true;
+ });
+ return res;
+}
+
+DomItem DomItem::component(GoTo options) const
+{
+ if (DomItem res = filterUp(
+ [](DomType kind, const DomItem &) {
+ return kind == DomType::QmlComponent || kind == DomType::QmltypesComponent
+ || kind == DomType::GlobalComponent;
+ },
+ FilterUpOptions::ReturnInner))
+ return res;
+ if (options == GoTo::MostLikely) {
+ DomItem item = *this;
+ DomType kind = item.internalKind();
+ if (kind == DomType::List || kind == DomType::Map) {
+ item = item.containingObject();
+ kind = item.internalKind();
+ }
+ switch (kind) {
+ case DomType::ExternalItemPair:
+ case DomType::ExternalItemInfo:
+ item = fileObject(options);
+ Q_FALLTHROUGH();
+ case DomType::QmlFile:
+ return item.field(Fields::components).key(QString()).index(0);
+ default:
+ break;
+ }
}
return DomItem();
}
@@ -575,12 +753,21 @@ struct ResolveToDo {
int pathIndex;
};
-bool DomItem::resolve(Path path,
- DomItem::Visitor visitor,
- ErrorHandler errorHandler,
- ResolveOptions options,
- Path fullPath,
- QList<Path> *visitedRefs) const
+static QMap<LookupType, QString> lookupTypeToStringMap()
+{
+ static QMap<LookupType, QString> map = []() {
+ QMetaEnum metaEnum = QMetaEnum::fromType<LookupType>();
+ QMap<LookupType, QString> res;
+ for (int i = 0; i < metaEnum.keyCount(); ++i) {
+ res[LookupType(metaEnum.value(i))] = QString::fromUtf8(metaEnum.key(i));
+ }
+ return res;
+ }();
+ return map;
+}
+
+bool DomItem::resolve(const Path &path, DomItem::Visitor visitor, const ErrorHandler &errorHandler,
+ ResolveOptions options, const Path &fullPath, QList<Path> *visitedRefs) const
{
QList<Path> vRefs;
Path fPath = fullPath;
@@ -588,7 +775,7 @@ bool DomItem::resolve(Path path,
fPath = path;
if (path.length()==0)
return visitor(fPath, *this);
- QSet<QPair<quintptr,int> > visited;
+ QList<QSet<quintptr>> visited(path.length() + 1);
Path myPath = path;
QVector<ResolveToDo> toDos(1); // invariant: always increase pathIndex to guarantee end even with only partial visited match
if (path.headKind() == Path::Kind::Root) {
@@ -617,32 +804,31 @@ bool DomItem::resolve(Path path,
myResolveErrors().error(tr("Root context %1 is not known").arg(path.headName())).handle(errorHandler);
return false;
}
- toDos[0] = {root, 1};
+ toDos[0] = {std::move(root), 1};
} else {
toDos[0] = {*this, 0};
}
while (!toDos.isEmpty()) {
- auto toDo = toDos.last();
- toDos.removeLast();
+ const ResolveToDo toDo = toDos.takeLast();
{
- auto idNow = toDo.item.base()->id();
+ auto idNow = toDo.item.id();
if (idNow == quintptr(0) && toDo.item == *this)
- idNow = quintptr(base());
- if (idNow != quintptr(0) && visited.contains(qMakePair(idNow,0)))
+ idNow = quintptr(this);
+ if (idNow != quintptr(0) && visited[0].contains(idNow))
continue;
}
int iPath = toDo.pathIndex;
DomItem it = toDo.item;
bool branchExhausted = false;
while (iPath < path.length() && it && !branchExhausted) {
- auto idNow = it.base()->id();
+ auto idNow = it.id();
if (idNow == quintptr() && toDo.item == *this)
- idNow = quintptr(base());
+ idNow = quintptr(this);
if (idNow != quintptr(0)) {
auto vPair = qMakePair(idNow, iPath);
- if (visited.contains(vPair))
+ if (visited[vPair.second].contains(vPair.first))
break;
- visited.insert(vPair);
+ visited[vPair.second].insert(vPair.first);
}
if (options & ResolveOption::TraceVisit && !visitor(path.mid(0,iPath), it))
return false;
@@ -654,28 +840,34 @@ bool DomItem::resolve(Path path,
case Path::Kind::Field:
if (cNow.checkHeadName(Fields::get) && it.internalKind() == DomType::Reference) {
Path toResolve = it.as<Reference>()->referredObjectPath;
+ Path refRef = it.canonicalPath();
if (visitedRefs == nullptr) {
visitedRefs = &vRefs;
- visitedRefs->append(fPath);
}
- if (visitedRefs->contains(toResolve)) {
- myResolveErrors().error([visitedRefs, toResolve](Sink sink) {
- sink(tr("Circular reference:"));
- sink(u"\n");
- for (const Path &vPath : *visitedRefs) {
- sink(u" ");
- vPath.dump(sink);
- sink(u" >\n");
- }
- toResolve.dump(sink);
- }).handle(errorHandler);
+ if (visitedRefs->contains(refRef)) {
+ myResolveErrors()
+ .error([visitedRefs, refRef](const Sink &sink) {
+ const QString msg = tr("Circular reference:") + QLatin1Char('\n');
+ sink(QStringView{msg});
+ for (const Path &vPath : *visitedRefs) {
+ sink(u" ");
+ vPath.dump(sink);
+ sink(u" >\n");
+ }
+ refRef.dump(sink);
+ })
+ .handle(errorHandler);
it = DomItem();
} else {
+ visitedRefs->append(refRef);
DomItem resolveRes;
- it.resolve(toResolve, [&resolveRes](Path, const DomItem &r) {
- resolveRes = r;
- return false;
- }, errorHandler, ResolveOption::None, toResolve, visitedRefs);
+ it.resolve(
+ toResolve,
+ [&resolveRes](Path, const DomItem &r) {
+ resolveRes = r;
+ return false;
+ },
+ errorHandler, ResolveOption::None, toResolve, visitedRefs);
it = resolveRes;
}
} else {
@@ -706,13 +898,17 @@ bool DomItem::resolve(Path path,
return false;
}
if (!branchExhausted)
- visitChildren(Path(),[toFind, &toDos, iPath](Path, const DomItem &item, bool) {
- // avoid non directly attached?
- DomItem newItem = item[toFind];
- if (newItem)
- toDos.append({newItem, iPath});
- return true;
- }, VisitOption::Recurse | VisitOption::VisitAdopted | VisitOption::NoPath);
+ visitTree(
+ Path(),
+ [&toFind, &toDos, iPath](Path, const DomItem &item, bool) {
+ // avoid non directly attached?
+ DomItem newItem = item[toFind];
+ if (newItem)
+ toDos.append({ std::move(newItem), iPath });
+ return true;
+ },
+ VisitOption::VisitSelf | VisitOption::Recurse
+ | VisitOption::VisitAdopted | VisitOption::NoPath);
branchExhausted = true;
break;
}
@@ -730,64 +926,170 @@ bool DomItem::resolve(Path path,
if (domKind() != DomKind::Object)
it = it.containingObject();
break;
- case PathCurrent::ObjChain:
-
- case PathCurrent::ScopeChain:
+ case PathCurrent::ObjChain: {
+ bool cont = it.visitPrototypeChain(
+ [&toDos, iPath](const DomItem &subEl) {
+ toDos.append({ subEl, iPath });
+ return true;
+ },
+ VisitPrototypesOption::Normal, errorHandler, nullptr,
+ visitedRefs); // avoid passing visitedRefs?
+ if (!cont)
+ return false;
+ branchExhausted = true;
+ break;
+ }
+ case PathCurrent::ScopeChain: {
+ bool cont = it.visitScopeChain(
+ [&toDos, iPath](const DomItem &subEl) {
+ toDos.append({ subEl, iPath });
+ return true;
+ },
+ LookupOption::Normal, errorHandler);
+ if (!cont)
+ return false;
+ branchExhausted = true;
+ break;
+ }
case PathCurrent::Component:
it = it.component();
break;
case PathCurrent::Module:
case PathCurrent::Ids:
- it = it.component().field(Fields::ids);
+ it = it.component().ids();
break;
case PathCurrent::Types:
it = it.component()[Fields::exports];
break;
case PathCurrent::LookupStrict:
case PathCurrent::LookupDynamic:
- case PathCurrent::Lookup:
+ case PathCurrent::Lookup: {
+ LookupOptions opt = LookupOption::Normal;
if (current == PathCurrent::Lookup) {
- DomItem strict = it.component().field(u"~strictLookup~");
- if (!strict)
- strict = it.environment().field(u"defaultStrictLookup");
+ DomItem comp = it.component();
+ DomItem strict = comp.field(u"~strictLookup~");
+ if (!strict) {
+ DomItem env = it.environment();
+ strict = env.field(u"defaultStrictLookup");
+ }
if (strict && strict.value().toBool())
- current = PathCurrent::LookupStrict;
- else
- current = PathCurrent::LookupDynamic;
+ opt = opt | LookupOption::Strict;
+ } else if (current == PathCurrent::LookupStrict) {
+ opt = opt | LookupOption::Strict;
+ }
+ if (it.internalKind() == DomType::ScriptExpression) {
+ myResolveErrors()
+ .error(tr("Javascript lookups not yet implemented"))
+ .handle(errorHandler);
+ return false;
+ }
+ // enter lookup
+ auto idNow = it.id();
+ if (idNow == quintptr(0) && toDo.item == *this)
+ idNow = quintptr(this);
+ if (idNow != quintptr(0)) {
+ auto vPair = qMakePair(idNow, iPath);
+ if (visited[vPair.second].contains(vPair.first))
+ break;
+ visited[vPair.second].insert(vPair.first);
+ }
+ if (options & ResolveOption::TraceVisit && !visitor(path.mid(0, iPath), it))
+ return false;
+ if (iPath + 1 >= path.length()) {
+ myResolveErrors()
+ .error(tr("Premature end of path, expected a field specifying the "
+ "type, and a key specifying the name to search after a "
+ "lookup directive in %2")
+ .arg(path.toString()))
+ .handle(errorHandler);
+ return false;
}
- if (current == PathCurrent::LookupStrict) {
- myResolveErrors().error(tr("@lookupStrict unimplemented"))
+ Path cNow = path[iPath++];
+ if (cNow.headKind() != Path::Kind::Field) {
+ myResolveErrors()
+ .error(tr("Expected a key path specifying the type to search after "
+ "a lookup directive, not %1 at component %2 of %3")
+ .arg(cNow.toString())
+ .arg(iPath)
+ .arg(path.toString()))
.handle(errorHandler);
return false;
- } else if (current == PathCurrent::LookupDynamic) {
- // expand, add self, prototype, components, prototype, global
- DomItem proto = it.component()[u"~prototype~"];
- if (proto) {
- DomItem pVal = proto[u"get"];
- if (!pVal) {
- myResolveErrors().warning(tr("Could not find prototype %1").arg(proto.toString()))
- .handle(errorHandler);
- } else {
- toDos.append({proto, iPath - 1});
+ }
+ QString expectedType = cNow.headName();
+ LookupType lookupType = LookupType::Symbol;
+ {
+ bool found = false;
+ auto m = lookupTypeToStringMap();
+ auto it = m.begin();
+ auto end = m.end();
+ while (it != end) {
+ if (it.value().compare(expectedType, Qt::CaseInsensitive) == 0) {
+ lookupType = it.key();
+ found = true;
}
+ ++it;
}
- myResolveErrors().error(tr("@lookupDynamic unimplemented"))
+ if (!found) {
+ QString types;
+ it = lookupTypeToStringMap().begin();
+ while (it != end) {
+ if (!types.isEmpty())
+ types += QLatin1String("', '");
+ types += it.value();
+ ++it;
+ }
+ myResolveErrors()
+ .error(tr("Type for lookup was expected to be one of '%1', not "
+ "%2")
+ .arg(types, expectedType))
+ .handle(errorHandler);
+ return false;
+ }
+ }
+ cNow = path[iPath++];
+ if (cNow.headKind() != Path::Kind::Key) {
+ myResolveErrors()
+ .error(tr("Expected a key specifying the path to search after the "
+ "@lookup directive and type, not %1 at component %2 of "
+ "%3")
+ .arg(cNow.toString())
+ .arg(iPath)
+ .arg(path.toString()))
.handle(errorHandler);
return false;
- } else {
- myResolveErrors().error(tr("Unexpected Current path component %1").arg(cNow.headName()))
+ }
+ QString target = cNow.headName();
+ if (target.isEmpty()) {
+ myResolveErrors()
+ .warning(tr("Path with empty lookup at component %1 of %2 will "
+ "match nothing in %3.")
+ .arg(iPath)
+ .arg(path.toString())
+ .arg(it.canonicalPath().toString()))
.handle(errorHandler);
- return false;
+ return true;
}
+ it.visitLookup(
+ target,
+ [&toDos, iPath](const DomItem &subEl) {
+ toDos.append({ subEl, iPath });
+ return true;
+ },
+ lookupType, opt, errorHandler, &(visited[iPath]), visitedRefs);
+ branchExhausted = true;
break;
}
+ }
break;
}
case Path::Kind::Any:
- visitChildren(Path(), [&toDos, iPath](Path, const DomItem &item, bool) {
- toDos.append({item, iPath});
- return true;
- }, VisitOption::VisitAdopted);
+ visitTree(
+ Path(),
+ [&toDos, iPath](Path, const DomItem &item, bool) {
+ toDos.append({ item, iPath });
+ return true;
+ },
+ VisitOption::VisitSelf | VisitOption::Recurse | VisitOption::VisitAdopted);
branchExhausted = true;
break;
case Path::Kind::Filter:
@@ -803,420 +1105,1476 @@ bool DomItem::resolve(Path path,
return true;
}
-DomItem DomItem::path(Path p, ErrorHandler errorHandler) const
+DomItem DomItem::path(const Path &p, const ErrorHandler &errorHandler) const
{
if (!p)
return *this;
DomItem res;
- resolve(p, [&res](Path, DomItem it) {
+ resolve(p, [&res](const Path &, const DomItem &it) {
res = it;
return false;
}, errorHandler);
return res;
}
-DomItem DomItem::path(QString p, ErrorHandler errorHandler) const
+DomItem DomItem::path(const QString &p, const ErrorHandler &errorHandler) const
{
return path(Path::fromString(p, errorHandler));
}
-DomItem DomItem::path(QStringView p, ErrorHandler errorHandler) const
+DomItem DomItem::path(QStringView p, const ErrorHandler &errorHandler) const
{
return path(Path::fromString(p, errorHandler));
}
-QList<QString> const DomItem::fields() const
+QList<QString> DomItem::fields() const
{
- return base()->fields(*this);
+ return visitEl([this](auto &&el) { return el->fields(*this); });
}
DomItem DomItem::field(QStringView name) const
{
- return base()->field(*this, name);
+ return visitEl([this, name](auto &&el) { return el->field(*this, name); });
}
index_type DomItem::indexes() const
{
- return base()->indexes(*this);
+ return visitEl([this](auto &&el) { return el->indexes(*this); });
}
DomItem DomItem::index(index_type i) const
{
- return base()->index(*this, i);
+ return visitEl([this, i](auto &&el) { return el->index(*this, i); });
}
-QSet<QString> const DomItem::keys() const
+bool DomItem::visitIndexes(function_ref<bool(const DomItem &)> visitor) const
{
- return base()->keys(*this);
+ // use iterateDirectSubpathsConst instead?
+ int nIndexes = indexes();
+ for (int i = 0; i < nIndexes; ++i) {
+ DomItem v = index(i);
+ if (!visitor(v))
+ return false;
+ }
+ return true;
}
-DomItem DomItem::key(QString name) const
+QSet<QString> DomItem::keys() const
{
- return base()->key(*this, name);
+ return visitEl([this](auto &&el) { return el->keys(*this); });
}
-bool DomItem::visitChildren(
- Path basePath,
- DomItem::ChildrenVisitor visitor,
- VisitOptions options,
- DomItem::ChildrenVisitor openingVisitor,
- DomItem::ChildrenVisitor closingVisitor) const
+QStringList DomItem::sortedKeys() const
{
- if (!*this)
- return true;
- if (visitor && ! visitor(basePath, *this, true))
- return false;
- if (openingVisitor && !openingVisitor(basePath, *this, true))
- return true;
- auto atEnd = qScopeGuard([closingVisitor, basePath, this](){
- if (closingVisitor)
- closingVisitor(basePath, *this, true);
- });
- return base()->iterateDirectSubpathsConst(*this,
- [this, basePath, visitor, openingVisitor, closingVisitor, options]
- (Path p, const DomItem &item)
- {
- Path pNow;
- if (!(options & VisitOption::NoPath))
- pNow = basePath.subPath(p);
- if (item.containingObject() != *this) {
- if (!(options & VisitOption::VisitAdopted))
- return true;
- if (visitor && !visitor(pNow, item, false))
- return false;
- if (openingVisitor && !openingVisitor(pNow, item, false))
- return true;
- if (closingVisitor)
- closingVisitor(pNow, item, false);
- } else {
- return item.visitChildren(pNow, visitor, options, openingVisitor, closingVisitor);
- }
- return true;
- });
+ QSet<QString> ks = keys();
+ QStringList sortedKs(ks.begin(), ks.end());
+ std::sort(sortedKs.begin(), sortedKs.end());
+ return sortedKs;
}
-DomItem DomItem::operator[](const QString &cName) const
+DomItem DomItem::key(const QString &name) const
{
- if (internalKind() == DomType::Map)
- return key(cName);
- return field(cName);
+ return visitEl([this, name](auto &&el) { return el->key(*this, name); });
}
-DomItem DomItem::operator[](QStringView cName) const
+bool DomItem::visitKeys(function_ref<bool(const QString &, const DomItem &)> visitor) const
{
- if (internalKind() == DomType::Map)
- return key(cName.toString());
- return field(cName);
+ // use iterateDirectSubpathsConst instead?
+ const QStringList keys = sortedKeys();
+ for (const QString &k : keys) {
+ DomItem v = key(k);
+ if (!visitor(k, v))
+ return false;
+ }
+ return true;
}
-DomItem DomItem::operator[](Path p) const
+QList<DomItem> DomItem::values() const
{
- return path(p);
+ QList<DomItem> res;
+ visitEl([this, &res](const auto &el) {
+ return el->iterateDirectSubpathsConst(
+ *this, [&res](const PathEls::PathComponent &, function_ref<DomItem()> item) {
+ res.append(item());
+ return true;
+ });
+ });
+ return res;
}
-QCborValue DomItem::value() const
+void DomItem::writeOutPre(OutWriter &ow) const
{
- if (internalKind() == DomType::ConstantData)
- return static_cast<ConstantData const *>(base())->value();
- return QCborValue();
+ if (hasAnnotations()) {
+ DomItem anns = field(Fields::annotations);
+ for (const auto &ann : anns.values()) {
+ if (ann.annotations().indexes() == 0) {
+ ow.ensureNewline();
+ ann.writeOut(ow);
+ ow.ensureNewline();
+ } else {
+ DomItem annAnn = ann.annotations();
+ Q_ASSERT_X(annAnn.indexes() == 1 && annAnn.index(0).name() == u"duplicate",
+ "DomItem::writeOutPre", "Unexpected annotation of annotation");
+ }
+ }
+ }
+ ow.itemStart(*this);
}
-void DomItem::dumpPtr(Sink sink) const
+void DomItem::writeOut(OutWriter &ow) const
{
- sink(u"DomItem{ topPtr:");
- sink(QString::number((quintptr)m_top.get(),16));
- sink(u", ownerPtr:");
- sink(QString::number((quintptr)m_owner.get(),16));
- sink(u", m_basePtr:");
- sink(QString::number((quintptr)m_base,16));
- sink(u", basePtr:");
- sink(QString::number((quintptr)base(),16));
- sink(u"}");
+ writeOutPre(ow);
+ visitEl([this, &ow](auto &&el) { el->writeOut(*this, ow); });
+ writeOutPost(ow);
}
-void DomItem::dump(Sink s, int indent) const
+void DomItem::writeOutPost(OutWriter &ow) const
{
- base()->dump(*this, s, indent);
+ ow.itemEnd(*this);
}
-QString DomItem::toString() const
+DomItem::WriteOutCheckResult DomItem::performWriteOutChecks(const DomItem &original, const DomItem &reformatted,
+ OutWriter &ow,
+ WriteOutChecks extraChecks) const
{
- return dumperToString([this](Sink s){ dump(s); });
+ QStringList dumped;
+ auto maybeDump = [&ow, extraChecks, &dumped](const DomItem &obj, QStringView objName) {
+ QString objDumpPath;
+ if (extraChecks & WriteOutCheck::DumpOnFailure) {
+ objDumpPath = QDir(QDir::tempPath())
+ .filePath(objName.toString()
+ + QFileInfo(ow.lineWriter.fileName()).baseName()
+ + QLatin1String(".dump.json"));
+ obj.dump(objDumpPath);
+ dumped.append(objDumpPath);
+ }
+ return objDumpPath;
+ };
+ auto dumpedDumper = [&dumped](const Sink &s) {
+ if (dumped.isEmpty())
+ return;
+ s(u"\ndump: ");
+ for (const auto &dumpPath : dumped) {
+ s(u" ");
+ sinkEscaped(s, dumpPath);
+ }
+ };
+ auto compare = [&maybeDump, &dumpedDumper, this](const DomItem &obj1, QStringView obj1Name,
+ const DomItem &obj2, QStringView obj2Name,
+ const FieldFilter &filter) {
+ const auto diffList = domCompareStrList(obj1, obj2, filter, DomCompareStrList::AllDiffs);
+ if (!diffList.isEmpty()) {
+ maybeDump(obj1, obj1Name);
+ maybeDump(obj2, obj2Name);
+ qCWarning(writeOutLog).noquote().nospace()
+ << obj2Name << " writeOut of " << this->canonicalFilePath() << " has changes:\n"
+ << diffList.join(QString()) << dumpedDumper;
+ return false;
+ }
+ return true;
+ };
+ auto checkStability = [&maybeDump, &dumpedDumper, &dumped, &ow,
+ this](const QString &expected, const DomItem &obj, QStringView objName) {
+ LineWriter lw2([](QStringView) {}, ow.lineWriter.fileName(), ow.lineWriter.options());
+ OutWriter ow2(lw2);
+ ow2.indentNextlines = true;
+ obj.writeOut(ow2);
+ ow2.eof();
+ if (ow2.writtenStr != expected) {
+ DomItem fObj = this->fileObject();
+ maybeDump(fObj, u"initial");
+ maybeDump(obj, objName);
+ qCWarning(writeOutLog).noquote().nospace()
+ << objName << " non stable writeOut of " << this->canonicalFilePath() << ":"
+ << lineDiff(ow2.writtenStr, expected, 2) << dumpedDumper;
+ dumped.clear();
+ return false;
+ }
+ return true;
+ };
+
+ if ((extraChecks & WriteOutCheck::UpdatedDomCompare)
+ && !compare(original, u"initial", reformatted, u"reformatted",
+ FieldFilter::noLocationFilter()))
+ return WriteOutCheckResult::Failed;
+
+ if (extraChecks & WriteOutCheck::UpdatedDomStable) {
+ checkStability(ow.writtenStr, reformatted, u"reformatted");
+ }
+
+ if (extraChecks
+ & (WriteOutCheck::Reparse | WriteOutCheck::ReparseCompare | WriteOutCheck::ReparseStable)) {
+ DomItem newEnv = environment().makeCopy().item();
+ std::shared_ptr<DomEnvironment> newEnvPtr = newEnv.ownerAs<DomEnvironment>();
+ if (!newEnvPtr)
+ return WriteOutCheckResult::Failed;
+
+ auto newFilePtr = std::make_shared<QmlFile>(canonicalFilePath(), ow.writtenStr);
+ if (!newFilePtr)
+ return WriteOutCheckResult::Failed;
+ newEnvPtr->addQmlFile(newFilePtr, AddOption::Overwrite);
+
+ DomItem newFile = newEnv.copy(newFilePtr, Path());
+ if (newFilePtr->isValid()) {
+ if (extraChecks & (WriteOutCheck::ReparseCompare | WriteOutCheck::ReparseStable)) {
+ newEnvPtr->populateFromQmlFile(newFile);
+ if ((extraChecks & WriteOutCheck::ReparseCompare)
+ && !compare(reformatted, u"reformatted", newFile, u"reparsed",
+ FieldFilter::compareNoCommentsFilter()))
+ return WriteOutCheckResult::Failed;
+ if ((extraChecks & WriteOutCheck::ReparseStable))
+ checkStability(ow.writtenStr, newFile, u"reparsed");
+ }
+ } else {
+ const auto iterateErrors = [&newFile](const Sink &s) {
+ newFile.iterateErrors(
+ [s](const DomItem &, const ErrorMessage &msg) {
+ s(u"\n ");
+ msg.dump(s);
+ return true;
+ },
+ true);
+ s(u"\n"); // extra empty line at the end...
+ };
+ qCWarning(writeOutLog).noquote().nospace()
+ << "writeOut of " << canonicalFilePath()
+ << " created invalid code:\n----------\n"
+ << ow.writtenStr << "\n----------" << iterateErrors;
+ return WriteOutCheckResult::Failed;
+ }
+ }
+ return WriteOutCheckResult::Success;
}
-int DomItem::derivedFrom() const
-{
- if (m_owner)
- return m_owner->derivedFrom();
- return 0;
+/*!
+ \internal
+ Performes WriteOut of the FileItem and verifies the consistency of the DOM structure.
+
+ OutWriter is essentially a visitor traversing the DOM structure, starting from
+ the current item representing a FileItem.
+ While traversing it might be saving some intermediate information, used later for restoring
+ written out item. Restoration is needed to validate that the DOM structure of the written item
+ has not changed.
+*/
+bool DomItem::writeOutForFile(OutWriter &ow, WriteOutChecks extraChecks) const
+{
+ ow.indentNextlines = true;
+ writeOut(ow);
+ ow.eof();
+
+ auto currentFileItem = fileObject();
+ auto writtenFileItem = ow.restoreWrittenFileItem(currentFileItem);
+ WriteOutCheckResult result = WriteOutCheckResult::Success;
+ if (extraChecks & WriteOutCheck::All)
+ result = performWriteOutChecks(currentFileItem, writtenFileItem, ow, extraChecks);
+ return result == WriteOutCheckResult::Success ? bool(writtenFileItem) : false;
+}
+
+bool DomItem::writeOut(const QString &path, int nBackups, const LineWriterOptions &options,
+ FileWriter *fw, WriteOutChecks extraChecks) const
+{
+ FileWriter localFw;
+ if (!fw)
+ fw = &localFw;
+ auto status = fw->write(
+ path,
+ [this, path, &options, extraChecks](QTextStream &ts) {
+ LineWriter lw([&ts](QStringView s) { ts << s; }, path, options);
+ OutWriter ow(lw);
+ return writeOutForFile(ow, extraChecks);
+ },
+ nBackups);
+ switch (status) {
+ case FileWriter::Status::DidWrite:
+ case FileWriter::Status::SkippedEqual:
+ return true;
+ case FileWriter::Status::ShouldWrite:
+ case FileWriter::Status::SkippedDueToFailure:
+ qCWarning(writeOutLog) << "failure reformatting " << path;
+ return false;
+ default:
+ qCWarning(writeOutLog) << "Unknown FileWriter::Status ";
+ Q_ASSERT(false);
+ return false;
+ }
}
-int DomItem::revision() const {
- if (m_owner)
- return m_owner->revision();
- else
- return -1;
+bool DomItem::isCanonicalChild(const DomItem &item) const
+{
+ bool isChild = false;
+ if (item.isOwningItem()) {
+ isChild = canonicalPath() == item.canonicalPath().dropTail();
+ } else {
+ DomItem itemOw = item.owner();
+ DomItem selfOw = owner();
+ isChild = itemOw == selfOw && item.pathFromOwner().dropTail() == pathFromOwner();
+ }
+ return isChild;
}
-QDateTime DomItem::createdAt() const
+bool DomItem::hasAnnotations() const
{
- if (m_owner)
- return m_owner->createdAt();
- else
- return QDateTime::fromMSecsSinceEpoch(0);
+ bool hasAnnotations = false;
+ DomType iKind = internalKind();
+ switch (iKind) {
+ case DomType::Id:
+ if (const Id *myPtr = as<Id>())
+ hasAnnotations = !myPtr->annotations.isEmpty();
+ break;
+ case DomType::PropertyDefinition:
+ if (const PropertyDefinition *myPtr = as<PropertyDefinition>())
+ hasAnnotations = !myPtr->annotations.isEmpty();
+ break;
+ case DomType::MethodInfo:
+ if (const MethodInfo *myPtr = as<MethodInfo>())
+ hasAnnotations = !myPtr->annotations.isEmpty();
+ break;
+ case DomType::QmlObject:
+ if (const QmlObject *myPtr = as<QmlObject>())
+ hasAnnotations = !myPtr->annotations().isEmpty();
+ break;
+ case DomType::Binding:
+ if (const Binding *myPtr = as<Binding>())
+ hasAnnotations = !myPtr->annotations().isEmpty();
+ break;
+ default:
+ break;
+ }
+ return hasAnnotations;
}
-QDateTime DomItem::frozenAt() const
+/*!
+ \internal
+ \brief Visits recursively all the children of this item using the given visitors.
+
+ First, the visitor is called and can continue or exit the visit by returning true or false.
+
+ Second, the openingVisitor is called and controls if the children of the current item needs to
+ be visited or not by returning true or false. In either case, the visitation of all the other
+ siblings is not affected. If both visitor and openingVisitor returned true, then the childrens of
+ the current item will be recursively visited.
+
+ Finally, after all the children were visited by visitor and openingVisitor, the closingVisitor
+ is called. Its return value is currently ignored.
+
+ Compared to the AST::Visitor*, openingVisitor and closingVisitor are called in the same order as
+ the visit() and endVisit()-calls.
+
+ Filtering allows to not visit certain part of the trees, and is checked before(!) the lazy child
+ is instantiated via its lambda. For example, visiting propertyInfos or defaultPropertyname takes
+ a lot of time because it resolves and collects all properties inherited from base types, and
+ might not even be relevant for the visitors.
+ */
+bool DomItem::visitTree(const Path &basePath, DomItem::ChildrenVisitor visitor,
+ VisitOptions options, DomItem::ChildrenVisitor openingVisitor,
+ DomItem::ChildrenVisitor closingVisitor, const FieldFilter &filter) const
+{
+ if (!*this)
+ return true;
+ if (options & VisitOption::VisitSelf && !visitor(basePath, *this, true))
+ return false;
+ if (options & VisitOption::VisitSelf && !openingVisitor(basePath, *this, true))
+ return true;
+ auto atEnd = qScopeGuard([closingVisitor, basePath, this, options]() {
+ if (options & VisitOption::VisitSelf) {
+ closingVisitor(basePath, *this, true);
+ }
+ });
+ return visitEl([this, basePath, visitor, openingVisitor, closingVisitor, options,
+ &filter](auto &&el) {
+ return el->iterateDirectSubpathsConst(
+ *this,
+ [this, basePath, visitor, openingVisitor, closingVisitor, options,
+ &filter](const PathEls::PathComponent &c, function_ref<DomItem()> itemF) {
+ Path pNow;
+ if (!(options & VisitOption::NoPath)) {
+ pNow = basePath;
+ pNow = pNow.appendComponent(c);
+ }
+ if (!filter(*this, c, DomItem{}))
+ return true;
+ DomItem item = itemF();
+ bool directChild = isCanonicalChild(item);
+ if (!directChild && !(options & VisitOption::VisitAdopted))
+ return true;
+ if (!directChild || !(options & VisitOption::Recurse)) {
+ if (!visitor(pNow, item, directChild))
+ return false;
+ // give an option to avoid calling open/close when not recursing?
+ // calling it always allows close to do the reverse looping (children before
+ // parent)
+ if (!openingVisitor(pNow, item, directChild))
+ return true;
+ closingVisitor(pNow, item, directChild);
+ } else {
+ return item.visitTree(pNow, visitor, options | VisitOption::VisitSelf,
+ openingVisitor, closingVisitor, filter);
+ }
+ return true;
+ });
+ });
+}
+static bool visitPrototypeIndex(QList<DomItem> &toDo, const DomItem &current,
+ const DomItem &derivedFromPrototype, const ErrorHandler &h,
+ QList<Path> *visitedRefs, VisitPrototypesOptions options,
+ const DomItem &prototype)
{
- if (m_owner)
- return m_owner->frozenAt();
+ Path elId = prototype.canonicalPath();
+ if (visitedRefs->contains(elId))
+ return true;
else
- return QDateTime::fromMSecsSinceEpoch(0);
+ visitedRefs->append(elId);
+ QList<DomItem> protos = prototype.getAll(h, visitedRefs);
+ if (protos.isEmpty()) {
+ if (std::shared_ptr<DomEnvironment> envPtr =
+ derivedFromPrototype.environment().ownerAs<DomEnvironment>())
+ if (!(envPtr->options() & DomEnvironment::Option::NoDependencies))
+ derivedFromPrototype.myErrors()
+ .warning(derivedFromPrototype.tr("could not resolve prototype %1 (%2)")
+ .arg(current.canonicalPath().toString(),
+ prototype.field(Fields::referredObjectPath)
+ .value()
+ .toString()))
+ .withItem(derivedFromPrototype)
+ .handle(h);
+ } else {
+ if (protos.size() > 1) {
+ QStringList protoPaths;
+ for (const DomItem &p : protos)
+ protoPaths.append(p.canonicalPath().toString());
+ derivedFromPrototype.myErrors()
+ .warning(derivedFromPrototype
+ .tr("Multiple definitions found, using first only, resolving "
+ "prototype %1 (%2): %3")
+ .arg(current.canonicalPath().toString(),
+ prototype.field(Fields::referredObjectPath)
+ .value()
+ .toString(),
+ protoPaths.join(QLatin1String(", "))))
+ .withItem(derivedFromPrototype)
+ .handle(h);
+ }
+ int nProtos = 1; // change to protos.length() to use all prototypes
+ // (sloppier)
+ for (int i = nProtos; i != 0;) {
+ DomItem proto = protos.at(--i);
+ if (proto.internalKind() == DomType::Export) {
+ if (!(options & VisitPrototypesOption::ManualProceedToScope))
+ proto = proto.proceedToScope(h, visitedRefs);
+ toDo.append(proto);
+ } else if (proto.internalKind() == DomType::QmlObject
+ || proto.internalKind() == DomType::QmlComponent) {
+ toDo.append(proto);
+ } else {
+ derivedFromPrototype.myErrors()
+ .warning(derivedFromPrototype.tr("Unexpected prototype type %1 (%2)")
+ .arg(current.canonicalPath().toString(),
+ prototype.field(Fields::referredObjectPath)
+ .value()
+ .toString()))
+ .withItem(derivedFromPrototype)
+ .handle(h);
+ }
+ }
+ }
+ return true;
}
-QDateTime DomItem::lastDataUpdateAt() const
+bool DomItem::visitPrototypeChain(function_ref<bool(const DomItem &)> visitor,
+ VisitPrototypesOptions options, const ErrorHandler &h,
+ QSet<quintptr> *visited, QList<Path> *visitedRefs) const
+{
+ QSet<quintptr> visitedLocal;
+ if (!visited)
+ visited = &visitedLocal;
+ QList<Path> refsLocal;
+ if (!visitedRefs)
+ visitedRefs = &refsLocal;
+ bool shouldVisit = !(options & VisitPrototypesOption::SkipFirst);
+ DomItem current = qmlObject();
+ if (!current) {
+ myErrors().warning(tr("Prototype chain called outside object")).withItem(*this).handle(h);
+ return true;
+ }
+ QList<DomItem> toDo({ current });
+ while (!toDo.isEmpty()) {
+ current = toDo.takeLast();
+ current = current.proceedToScope(h, visitedRefs);
+ if (visited->contains(current.id())) {
+ // to warn about circular dependencies a purely local visited trace is required
+ // as common ancestors of unrelated objects are valid and should be skipped
+ // so we do not to warn unless requested
+ if (options & VisitPrototypesOption::RevisitWarn)
+ myErrors()
+ .warning(tr("Detected multiple visit of %1 visiting prototypes of %2")
+ .arg(current.canonicalPath().toString(),
+ canonicalPath().toString()))
+ .withItem(*this)
+ .handle(h);
+ continue;
+ }
+ visited->insert(current.id());
+ if (shouldVisit && !visitor(current))
+ return false;
+ shouldVisit = true;
+ current.field(Fields::prototypes)
+ .visitIndexes([&toDo, &current, this, &h, visitedRefs, options](const DomItem &el) {
+ return visitPrototypeIndex(toDo, current, *this, h, visitedRefs, options, el);
+ });
+ }
+ return true;
+}
+
+bool DomItem::visitDirectAccessibleScopes(
+ function_ref<bool(const DomItem &)> visitor, VisitPrototypesOptions options,
+ const ErrorHandler &h, QSet<quintptr> *visited, QList<Path> *visitedRefs) const
{
- if (m_owner)
- return m_owner->lastDataUpdateAt();
- else
- return QDateTime::fromMSecsSinceEpoch(0);
+ // these are the scopes one can access with the . operator from the current location
+ // but currently not the attached types, which we should
+ DomType k = internalKind();
+ if (k == DomType::QmlObject)
+ return visitPrototypeChain(visitor, options, h, visited, visitedRefs);
+ if (visited && id() != 0) {
+ if (visited->contains(id()))
+ return true;
+ visited->insert(id());
+ }
+ if (k == DomType::Id || k == DomType::Reference || k == DomType::Export) {
+ // we go to the scope if it is clearly defined
+ DomItem v = proceedToScope(h, visitedRefs);
+ if (v.internalKind() == DomType::QmlObject)
+ return v.visitPrototypeChain(visitor, options, h, visited, visitedRefs);
+ }
+ if (k == DomType::Binding) {
+ // from a binding we can get to its value if it is a object
+ DomItem v = field(Fields::value);
+ if (v.internalKind() == DomType::QmlObject)
+ return v.visitPrototypeChain(visitor, options, h, visited, visitedRefs);
+ }
+ if (k == DomType::PropertyDefinition) {
+ // from a property definition we go to the type stored in it
+ DomItem t = field(Fields::type).proceedToScope(h, visitedRefs);
+ if (t.internalKind() == DomType::QmlObject)
+ return t.visitPrototypeChain(visitor, options, h, visited, visitedRefs);
+ }
+ if (!(options & VisitPrototypesOption::SkipFirst) && isScope() && !visitor(*this))
+ return false;
+ return true;
+}
+
+/*!
+ * \brief DomItem::visitStaticTypePrototypeChains
+ * \param visitor
+ * \param visitFirst
+ * \param visited
+ * \return
+ *
+ * visit the values JS reaches accessing a type directly: the values if it is a singleton or the
+ * attached type
+ */
+bool DomItem::visitStaticTypePrototypeChains(
+ function_ref<bool(const DomItem &)> visitor, VisitPrototypesOptions options,
+ const ErrorHandler &h, QSet<quintptr> *visited, QList<Path> *visitedRefs) const
+{
+ QSet<quintptr> visitedLocal;
+ if (!visited)
+ visited = &visitedLocal;
+ DomItem current = qmlObject();
+ DomItem comp = current.component();
+ if (comp.field(Fields::isSingleton).value().toBool(false)
+ && !current.visitPrototypeChain(visitor, options, h, visited, visitedRefs))
+ return false;
+ if (DomItem attachedT = current.component().field(Fields::attachedType).field(Fields::get))
+ if (!attachedT.visitPrototypeChain(
+ visitor, options & ~VisitPrototypesOptions(VisitPrototypesOption::SkipFirst), h,
+ visited, visitedRefs))
+ return false;
+ return true;
+}
+
+/*!
+ \brief Let the visitor visit the Dom Tree hierarchy of this DomItem.
+ */
+bool DomItem::visitUp(function_ref<bool(const DomItem &)> visitor) const
+{
+ Path p = canonicalPath();
+ while (p.length() > 0) {
+ DomItem current = top().path(p);
+ if (!visitor(current))
+ return false;
+ p = p.dropTail();
+ }
+ return true;
}
-void DomItem::addError(ErrorMessage msg) const
+/*!
+ \brief Let the visitor visit the QML scope hierarchy of this DomItem.
+ */
+bool DomItem::visitScopeChain(
+ function_ref<bool(const DomItem &)> visitor, LookupOptions options, const ErrorHandler &h,
+ QSet<quintptr> *visited, QList<Path> *visitedRefs) const
+{
+ QSet<quintptr> visitedLocal;
+ if (!visited)
+ visited = &visitedLocal;
+ QList<Path> visitedRefsLocal;
+ if (!visitedRefs)
+ visitedRefs = &visitedRefsLocal;
+ DomItem current = scope();
+ if (!current) {
+ myResolveErrors().warning(tr("Called visitScopeChain outside scopes")).handle(h);
+ return true;
+ }
+ QList<DomItem> toDo { current };
+ bool visitFirst = !(options & LookupOption::SkipFirstScope);
+ bool visitCurrent = visitFirst;
+ bool first = true;
+ QSet<quintptr> alreadyAddedComponentMaps;
+ while (!toDo.isEmpty()) {
+ DomItem current = toDo.takeLast();
+ if (visited->contains(current.id()))
+ continue;
+ visited->insert(current.id());
+ if (visitCurrent && !visitor(current))
+ return false;
+ visitCurrent = true;
+ switch (current.internalKind()) {
+ case DomType::QmlObject: {
+ if (!current.visitPrototypeChain(visitor, VisitPrototypesOption::SkipFirst, h, visited,
+ visitedRefs))
+ return false;
+ DomItem root = current.rootQmlObject();
+ if (root && root != current) {
+ first = false;
+ toDo.append(root);
+ } else if (DomItem next = current.scope(
+ FilterUpOptions::ReturnOuterNoSelf)) { // should be the component
+ toDo.append(next);
+ }
+ } break;
+ case DomType::ScriptExpression: // Js lexical scope
+ first = false;
+ if (DomItem next = current.scope(FilterUpOptions::ReturnOuterNoSelf))
+ toDo.append(next);
+ break;
+ case DomType::QmlComponent: { // ids/attached type
+ if ((options & LookupOption::Strict) == 0) {
+ if (DomItem comp = current.field(Fields::nextComponent))
+ toDo.append(comp);
+ }
+ if (first && visitFirst && (options & LookupOption::VisitTopClassType)
+ && *this == current) { // visit attached type if it is the top of the chain
+ if (DomItem attachedT = current.field(Fields::attachedType).field(Fields::get))
+ toDo.append(attachedT);
+ }
+ if (DomItem next = current.scope(FilterUpOptions::ReturnOuterNoSelf))
+ toDo.append(next);
+
+ DomItem owner = current.owner();
+ Path pathToComponentMap = current.pathFromOwner().dropTail(2);
+ DomItem componentMap = owner.path(pathToComponentMap);
+ if (alreadyAddedComponentMaps.contains(componentMap.id()))
+ break;
+ alreadyAddedComponentMaps.insert(componentMap.id());
+ const auto keys = componentMap.keys();
+ for (const QString &x : keys) {
+ DomItem componentList = componentMap.key(x);
+ for (int i = 0; i < componentList.indexes(); ++i) {
+ DomItem component = componentList.index(i);
+ if (component != current && !visited->contains(component.id()))
+ toDo.append(component);
+ }
+ }
+ first = false;
+ break;
+ }
+ case DomType::QmlFile: // subComponents, imported types
+ if (DomItem iScope =
+ current.field(Fields::importScope)) // treat file as a separate scope?
+ toDo.append(iScope);
+ first = false;
+ break;
+ case DomType::MethodInfo: // method arguments
+ first = false;
+ if (DomItem next = current.scope(FilterUpOptions::ReturnOuterNoSelf))
+ toDo.append(next);
+ break;
+ case DomType::ImportScope: // types
+ first = false;
+ if (auto globalC = globalScope().field(Fields::rootComponent))
+ toDo.append(globalC);
+ break;
+ case DomType::JsResource:
+ case DomType::GlobalComponent:
+ first = false;
+ if (DomItem next = current.field(Fields::objects).index(0))
+ toDo.append(next);
+ break;
+ case DomType::QmltypesComponent:
+ first = false;
+ break;
+ default:
+ first = false;
+ myResolveErrors()
+ .error(tr("Unexpected non scope object %1 (%2) reached in visitScopeChain")
+ .arg(domTypeToString(current.internalKind()),
+ current.canonicalPath().toString()))
+ .handle(h);
+ Q_ASSERT(false);
+ break;
+ }
+ }
+ return true;
+}
+
+bool DomItem::visitLookup1(
+ const QString &symbolName, function_ref<bool(const DomItem &)> visitor, LookupOptions opts,
+ const ErrorHandler &h, QSet<quintptr> *visited, QList<Path> *visitedRefs) const
{
- if (m_owner)
- m_owner->addError(this->copy(m_owner), msg.withItem(*this));
- else
- defaultErrorHandler(msg.withItem(*this));
+ return visitScopeChain(
+ [symbolName, visitor](const DomItem &obj) {
+ return obj.visitLocalSymbolsNamed(symbolName,
+ [visitor](const DomItem &el) { return visitor(el); });
+ },
+ opts, h, visited, visitedRefs);
}
-ErrorHandler DomItem::errorHandler() const
+class CppTypeInfo
{
- DomItem self = *this;
- return [self](ErrorMessage m){
- self.addError(m);
- };
+ Q_DECLARE_TR_FUNCTIONS(CppTypeInfo)
+public:
+ CppTypeInfo() = default;
+
+ static CppTypeInfo fromString(QStringView target, const ErrorHandler &h = nullptr)
+ {
+ CppTypeInfo res;
+ QRegularExpression reTarget = QRegularExpression(QRegularExpression::anchoredPattern(
+ uR"(QList<(?<list>[a-zA-Z_0-9:]+) *(?<listPtr>\*?)>|QMap< *(?<mapKey>[a-zA-Z_0-9:]+) *, *(?<mapValue>[a-zA-Z_0-9:]+) *(?<mapPtr>\*?)>|(?<baseType>[a-zA-Z_0-9:]+) *(?<ptr>\*?))"));
+
+ QRegularExpressionMatch m = reTarget.matchView(target);
+ if (!m.hasMatch()) {
+ DomItem::myResolveErrors()
+ .error(tr("Unexpected complex CppType %1").arg(target))
+ .handle(h);
+ }
+ res.baseType = m.captured(u"baseType");
+ res.isPointer = !m.captured(u"ptr").isEmpty();
+ if (!m.captured(u"list").isEmpty()) {
+ res.isList = true;
+ res.baseType = m.captured(u"list");
+ res.isPointer = !m.captured(u"listPtr").isEmpty();
+ }
+ if (!m.captured(u"mapValue").isEmpty()) {
+ res.isMap = true;
+ if (m.captured(u"mapKey") != u"QString") {
+ DomItem::myResolveErrors()
+ .error(tr("Unexpected complex CppType %1 (map with non QString key)")
+ .arg(target))
+ .handle(h);
+ }
+ res.baseType = m.captured(u"mapValue");
+ res.isPointer = !m.captured(u"mapPtr").isEmpty();
+ }
+ return res;
+ }
+
+ QString baseType;
+ bool isPointer = false;
+ bool isMap = false;
+ bool isList = false;
+};
+
+static bool visitForLookupType(const DomItem &el, LookupType lookupType,
+ function_ref<bool(const DomItem &)> visitor)
+{
+ bool correctType = false;
+ DomType iType = el.internalKind();
+ switch (lookupType) {
+ case LookupType::Binding:
+ correctType = (iType == DomType::Binding);
+ break;
+ case LookupType::Method:
+ correctType = (iType == DomType::MethodInfo);
+ break;
+ case LookupType::Property:
+ correctType = (iType == DomType::PropertyDefinition || iType == DomType::Binding);
+ break;
+ case LookupType::PropertyDef:
+ correctType = (iType == DomType::PropertyDefinition);
+ break;
+ case LookupType::Type:
+ correctType = (iType == DomType::Export); // accept direct QmlObject ref?
+ break;
+ default:
+ Q_ASSERT(false);
+ break;
+ }
+ if (correctType)
+ return visitor(el);
+ return true;
+}
+
+static bool visitQualifiedNameLookup(
+ const DomItem &newIt, const QStringList &subpath,
+ function_ref<bool(const DomItem &)> visitor, LookupType lookupType,
+ const ErrorHandler &errorHandler, QList<Path> *visitedRefs)
+{
+ QVector<ResolveToDo> lookupToDos(
+ { ResolveToDo{ newIt, 1 } }); // invariant: always increase pathIndex to guarantee
+ // end even with only partial visited match
+ QList<QSet<quintptr>> lookupVisited(subpath.size() + 1);
+ while (!lookupToDos.isEmpty()) {
+ ResolveToDo tNow = lookupToDos.takeFirst();
+ auto vNow = qMakePair(tNow.item.id(), tNow.pathIndex);
+ DomItem subNow = tNow.item;
+ int iSubPath = tNow.pathIndex;
+ Q_ASSERT(iSubPath < subpath.size());
+ QString subPathNow = subpath[iSubPath++];
+ DomItem scope = subNow.proceedToScope();
+ if (iSubPath < subpath.size()) {
+ if (vNow.first != 0) {
+ if (lookupVisited[vNow.second].contains(vNow.first))
+ continue;
+ else
+ lookupVisited[vNow.second].insert(vNow.first);
+ }
+ if (scope.internalKind() == DomType::QmlObject)
+ scope.visitDirectAccessibleScopes(
+ [&lookupToDos, &subPathNow, iSubPath](const DomItem &el) {
+ return el.visitLocalSymbolsNamed(
+ subPathNow, [&lookupToDos, iSubPath](const DomItem &subEl) {
+ lookupToDos.append({ subEl, iSubPath });
+ return true;
+ });
+ },
+ VisitPrototypesOption::Normal, errorHandler, &(lookupVisited[vNow.second]),
+ visitedRefs);
+ } else {
+ bool cont = scope.visitDirectAccessibleScopes(
+ [&visitor, &subPathNow, lookupType](const DomItem &el) -> bool {
+ if (lookupType == LookupType::Symbol)
+ return el.visitLocalSymbolsNamed(subPathNow, visitor);
+ else
+ return el.visitLocalSymbolsNamed(
+ subPathNow, [lookupType, &visitor](const DomItem &el) -> bool {
+ return visitForLookupType(el, lookupType, visitor);
+ });
+ },
+ VisitPrototypesOption::Normal, errorHandler, &(lookupVisited[vNow.second]),
+ visitedRefs);
+ if (!cont)
+ return false;
+ }
+ }
+ return true;
}
-void DomItem::clearErrors(ErrorGroups groups, bool iterate) const
+bool DomItem::visitLookup(
+ const QString &target, function_ref<bool(const DomItem &)> visitor, LookupType lookupType,
+ LookupOptions opts, const ErrorHandler &errorHandler, QSet<quintptr> *visited,
+ QList<Path> *visitedRefs) const
{
- if (m_owner) {
- m_owner->clearErrors(groups);
- if (iterate)
- iterateSubOwners([groups](DomItem i){
- i.clearErrors(groups, true);
+ if (target.isEmpty())
+ return true;
+ switch (lookupType) {
+ case LookupType::Binding:
+ case LookupType::Method:
+ case LookupType::Property:
+ case LookupType::PropertyDef:
+ case LookupType::Symbol:
+ case LookupType::Type: {
+ QStringList subpath = target.split(QChar::fromLatin1('.'));
+ if (subpath.size() == 1) {
+ return visitLookup1(subpath.first(), visitor, opts, errorHandler, visited, visitedRefs);
+ } else {
+ return visitLookup1(
+ subpath.at(0),
+ [&subpath, visitor, lookupType, &errorHandler,
+ visitedRefs](const DomItem &newIt) -> bool {
+ return visitQualifiedNameLookup(newIt, subpath, visitor, lookupType,
+ errorHandler, visitedRefs);
+ },
+ opts, errorHandler, visited, visitedRefs);
+ }
+ break;
+ }
+ case LookupType::CppType: {
+ QString baseTarget = CppTypeInfo::fromString(target, errorHandler).baseType;
+ DomItem localQmltypes = owner();
+ while (localQmltypes && localQmltypes.internalKind() != DomType::QmltypesFile) {
+ localQmltypes = localQmltypes.containingObject();
+ localQmltypes = localQmltypes.owner();
+ }
+ if (localQmltypes) {
+ if (DomItem localTypes = localQmltypes.field(Fields::components).key(baseTarget)) {
+ bool cont = localTypes.visitIndexes([&visitor](const DomItem &els) {
+ return els.visitIndexes([&visitor](const DomItem &el) {
+ if (DomItem obj = el.field(Fields::objects).index(0))
+ return visitor(obj);
+ return true;
+ });
+ });
+ if (!cont)
+ return false;
+ }
+ }
+ DomItem qmltypes = environment().field(Fields::qmltypesFileWithPath);
+ return qmltypes.visitKeys([baseTarget, &visitor](const QString &, const DomItem &els) {
+ DomItem comps =
+ els.field(Fields::currentItem).field(Fields::components).key(baseTarget);
+ return comps.visitIndexes([&visitor](const DomItem &el) {
+ if (DomItem obj = el.field(Fields::objects).index(0))
+ return visitor(obj);
return true;
});
+ });
+ break;
+ }
}
+ Q_ASSERT(false);
+ return true;
}
-bool DomItem::iterateErrors(function<bool (DomItem, ErrorMessage)> visitor, bool iterate,
- Path inPath) const
-{
- if (m_owner) {
- if (!m_owner->iterateErrors(owner(), visitor, inPath))
- return false;
- if (iterate && !iterateSubOwners([inPath, visitor](DomItem i){
- return i.iterateErrors(visitor, true, inPath);
- }))
- return false;
+/*!
+ \internal
+ \brief Dereference DomItems pointing to other DomItems.
+
+ Dereferences DomItems with internalKind being References, Export and Id.
+ Also does multiple rounds of resolving for nested DomItems.
+ Prefer this over \l {DomItem::get}.
+ */
+DomItem DomItem::proceedToScope(const ErrorHandler &h, QList<Path> *visitedRefs) const
+{
+ // follow references, resolve exports
+ DomItem current = *this;
+ while (current) {
+ switch (current.internalKind()) {
+ case DomType::Reference: {
+ Path currentPath = current.canonicalPath();
+ current = current.get(h, visitedRefs);
+ break;
+ }
+ case DomType::Export:
+ current = current.field(Fields::type);
+ break;
+ case DomType::Id:
+ current = current.field(Fields::referredObject);
+ break;
+ default:
+ return current.scope();
+ break;
+ }
}
- return true;
+ return DomItem();
}
-bool DomItem::iterateSubOwners(function<bool (DomItem)> visitor) const
+QList<DomItem> DomItem::lookup(const QString &symbolName, LookupType type, LookupOptions opts,
+ const ErrorHandler &errorHandler) const
{
- if (m_owner)
- return m_owner->iterateSubOwners(owner(), visitor);
- return true;
+ QList<DomItem> res;
+ visitLookup(
+ symbolName,
+ [&res](const DomItem &el) {
+ res.append(el);
+ return true;
+ },
+ type, opts, errorHandler);
+ return res;
}
-shared_ptr<DomTop> DomItem::topPtr() const
+DomItem DomItem::lookupFirst(const QString &symbolName, LookupType type, LookupOptions opts,
+ const ErrorHandler &errorHandler) const
{
- return m_top;
+ DomItem res;
+ visitLookup(
+ symbolName,
+ [&res](const DomItem &el) {
+ res = el;
+ return false;
+ },
+ type, opts, errorHandler);
+ return res;
}
-shared_ptr<OwningItem> DomItem::owningItemPtr() const
+quintptr DomItem::id() const
{
- return m_owner;
+ return visitEl([](auto &&b) { return b->id(); });
}
-DomItem DomItem::copy(shared_ptr<OwningItem> owner, DomBase *base) const
+Path DomItem::pathFromOwner() const
{
- return DomItem(m_top, owner, base);
+ return visitEl([this](auto &&e) { return e->pathFromOwner(*this); });
}
-DomItem DomItem::copy(shared_ptr<OwningItem> owner) const
+QString DomItem::canonicalFilePath() const
{
- return DomItem(m_top, owner, owner.get());
+ return visitEl([this](auto &&e) { return e->canonicalFilePath(*this); });
}
-DomItem DomItem::copy(DomBase *base) const
+DomItem DomItem::fileLocationsTree() const
{
- return DomItem(m_top, m_owner, base);
+ if (DomItem l = field(Fields::fileLocationsTree))
+ return l;
+ auto res = FileLocations::findAttachedInfo(*this);
+ if (res && res.foundTreePath) {
+ return copy(res.foundTree, res.foundTreePath);
+ }
+ return DomItem();
}
-Subpath DomItem::subDataField(QStringView fieldName, QCborValue value, ConstantData::Options options, const SourceLocation & loc) const
+DomItem DomItem::fileLocations() const
{
- return subDataPath(Path::field(fieldName), value, options, loc);
+ return fileLocationsTree().field(Fields::infoItem);
}
-Subpath DomItem::subDataField(QString fieldName, QCborValue value, ConstantData::Options options, const SourceLocation & loc) const
+MutableDomItem DomItem::makeCopy(DomItem::CopyOption option) const
{
- return subDataPath(Path::field(fieldName), value, options, loc);
+ if (m_kind == DomType::Empty)
+ return MutableDomItem();
+ DomItem o = owner();
+ if (option == CopyOption::EnvDisconnected) {
+ DomItem newItem = std::visit([this, &o](auto &&el) {
+ if constexpr (std::is_same_v<std::decay_t<decltype(el)>, std::monostate>) {
+ return DomItem();
+ } else {
+ auto copyPtr = el->makeCopy(o);
+ return DomItem(m_top, copyPtr, m_ownerPath, copyPtr.get());
+ }
+ }, m_owner);
+ return MutableDomItem(newItem.path(pathFromOwner()));
+ }
+ DomItem env = environment();
+ std::shared_ptr<DomEnvironment> newEnvPtr;
+ if (std::shared_ptr<DomEnvironment> envPtr = env.ownerAs<DomEnvironment>()) {
+ newEnvPtr = std::make_shared<DomEnvironment>(envPtr, envPtr->loadPaths(), envPtr->options(),
+ envPtr->domCreationOptions());
+ DomBase *eBase = envPtr.get();
+ if (std::holds_alternative<const DomEnvironment *>(m_element) && eBase
+ && std::get<const DomEnvironment *>(m_element) == eBase)
+ return MutableDomItem(DomItem(newEnvPtr));
+ } else if (std::shared_ptr<DomUniverse> univPtr = top().ownerAs<DomUniverse>()) {
+ newEnvPtr = std::make_shared<DomEnvironment>(
+ QStringList(),
+ DomEnvironment::Option::SingleThreaded | DomEnvironment::Option::NoDependencies,
+ DomCreationOption::None, univPtr);
+ } else {
+ Q_ASSERT(false);
+ return {};
+ }
+ DomItem newItem = std::visit(
+ [this, newEnvPtr, &o](auto &&el) {
+ if constexpr (std::is_same_v<std::decay_t<decltype(el)>, std::monostate>) {
+ return DomItem();
+ } else {
+ auto copyPtr = el->makeCopy(o);
+ return DomItem(newEnvPtr, copyPtr, m_ownerPath, copyPtr.get());
+ }
+ },
+ m_owner);
+
+ switch (o.internalKind()) {
+ case DomType::QmlDirectory:
+ newEnvPtr->addQmlDirectory(newItem.ownerAs<QmlDirectory>(), AddOption::Overwrite);
+ break;
+ case DomType::JsFile:
+ newEnvPtr->addJsFile(newItem.ownerAs<JsFile>(), AddOption::Overwrite);
+ break;
+ case DomType::QmlFile:
+ newEnvPtr->addQmlFile(newItem.ownerAs<QmlFile>(), AddOption::Overwrite);
+ break;
+ case DomType::QmltypesFile:
+ newEnvPtr->addQmltypesFile(newItem.ownerAs<QmltypesFile>(), AddOption::Overwrite);
+ break;
+ case DomType::GlobalScope: {
+ newEnvPtr->addGlobalScope(newItem.ownerAs<GlobalScope>(), AddOption::Overwrite);
+ } break;
+ case DomType::ModuleIndex:
+ case DomType::MockOwner:
+ case DomType::ScriptExpression:
+ case DomType::AstComments:
+ case DomType::LoadInfo:
+ case DomType::AttachedInfo:
+ case DomType::DomEnvironment:
+ case DomType::DomUniverse:
+ qCWarning(domLog) << "DomItem::makeCopy " << internalKindStr()
+ << " does not support binding to environment";
+ Q_ASSERT(false);
+ return MutableDomItem();
+ default:
+ qCWarning(domLog) << "DomItem::makeCopy(" << internalKindStr()
+ << ") is not an known OwningItem";
+ Q_ASSERT(o.isOwningItem());
+ return MutableDomItem();
+ }
+ DomItem newEnv(newEnvPtr);
+ Q_ASSERT(newEnv.path(o.canonicalPath()).m_owner == newItem.m_owner);
+ return MutableDomItem(newItem.path(pathFromOwner()));
}
-Subpath DomItem::subDataIndex(index_type i, QCborValue value, ConstantData::Options options, const SourceLocation & loc) const
+bool DomItem::commitToBase(const std::shared_ptr<DomEnvironment> &validEnvPtr) const
{
- return subDataPath(Path::index(i), value, options, loc);
+ DomItem env = environment();
+ if (std::shared_ptr<DomEnvironment> envPtr = env.ownerAs<DomEnvironment>()) {
+ return envPtr->commitToBase(env, validEnvPtr);
+ }
+ return false;
}
-Subpath DomItem::subDataKey(QStringView keyName, QCborValue value, ConstantData::Options options, const SourceLocation & loc) const
+bool DomItem::visitLocalSymbolsNamed(const QString &name, function_ref<bool(const DomItem &)> visitor) const
{
- return subDataPath(Path::key(keyName), value, options, loc);
+ if (name.isEmpty()) // no empty symbol
+ return true;
+ // we could avoid discriminating by type and just access all the needed stuff in the correct
+ // sequence, making sure it is empty if not provided, but for now I find it clearer to check the
+ // type
+ DomItem f;
+ DomItem v;
+ switch (internalKind()) {
+ case DomType::QmlObject:
+ f = field(Fields::propertyDefs);
+ v = f.key(name);
+ if (!v.visitIndexes(visitor))
+ return false;
+ f = field(Fields::bindings);
+ v = f.key(name);
+ if (!v.visitIndexes(visitor))
+ return false;
+ f = field(Fields::methods);
+ v = f.key(name);
+ if (!v.visitIndexes(visitor))
+ return false;
+ break;
+ case DomType::ScriptExpression:
+ // to do
+ break;
+ case DomType::QmlComponent:
+ f = field(Fields::ids);
+ v = f.key(name);
+ if (!v.visitIndexes(visitor))
+ return false;
+ Q_FALLTHROUGH();
+ case DomType::JsResource:
+ case DomType::GlobalComponent:
+ case DomType::QmltypesComponent:
+ f = field(Fields::enumerations);
+ v = f.key(name);
+ if (!v.visitIndexes(visitor))
+ return false;
+ break;
+ case DomType::MethodInfo: {
+ DomItem params = field(Fields::parameters);
+ if (!params.visitIndexes([name, visitor](const DomItem &p) {
+ const MethodParameter *pPtr = p.as<MethodParameter>();
+ if (pPtr->name == name && !visitor(p))
+ return false;
+ return true;
+ }))
+ return false;
+ break;
+ }
+ case DomType::QmlFile: {
+ f = field(Fields::components);
+ v = f.key(name);
+ if (!v.visitIndexes(visitor))
+ return false;
+ break;
+ }
+ case DomType::ImportScope: {
+ f = field(Fields::imported);
+ v = f.key(name);
+ if (!v.visitIndexes(visitor))
+ return false;
+ f = field(Fields::qualifiedImports);
+ v = f.key(name);
+ if (v && !visitor(v))
+ return false;
+ break;
+ default:
+ Q_ASSERT(!isScope());
+ break;
+ }
+ }
+ return true;
}
-Subpath DomItem::subDataKey(QString keyName, QCborValue value, ConstantData::Options options, const SourceLocation & loc) const
+DomItem DomItem::operator[](const QString &cName) const
{
- return subDataPath(Path::key(keyName), value, options, loc);
+ if (internalKind() == DomType::Map)
+ return key(cName);
+ return field(cName);
}
-Subpath DomItem::subDataPath(Path path, QCborValue value, ConstantData::Options options, const SourceLocation & loc) const
+DomItem DomItem::operator[](QStringView cName) const
{
- if (domTypeIsOwningItem(internalKind()))
- return Subpath{path, DomItem(m_top, m_owner,
- ConstantData(path, value, options, loc))};
- else
- return Subpath{path, DomItem(m_top, m_owner,
- ConstantData(pathFromOwner().subPath(path), value, options, loc))};
+ if (internalKind() == DomType::Map)
+ return key(cName.toString());
+ return field(cName);
}
-Subpath DomItem::subReferenceField(QStringView fieldName, Path referencedObject,
- const SourceLocation & loc) const
+DomItem DomItem::operator[](const Path &p) const
{
- return subReferencePath(Path::field(fieldName), referencedObject, loc);
+ return path(p);
}
-Subpath DomItem::subReferenceField(QString fieldName, Path referencedObject, const SourceLocation & loc) const
+QCborValue DomItem::value() const
{
- return subReferencePath(Path::field(fieldName), referencedObject, loc);
+ return base()->value();
}
-Subpath DomItem::subReferenceKey(QStringView keyName, Path referencedObject, const SourceLocation & loc) const
+void DomItem::dumpPtr(const Sink &sink) const
{
- return subReferencePath(Path::key(keyName), referencedObject,loc);
+ sink(u"DomItem{ topPtr:");
+ sink(QString::number((quintptr)topPtr().get(), 16));
+ sink(u", ownerPtr:");
+ sink(QString::number((quintptr)owningItemPtr().get(), 16));
+ sink(u", ownerPath:");
+ m_ownerPath.dump(sink);
+ sink(u", elPtr:");
+ sink(QString::number((quintptr)base(),16));
+ sink(u"}");
}
-Subpath DomItem::subReferenceKey(QString keyName, Path referencedObject, const SourceLocation & loc) const
+void DomItem::dump(
+ const Sink &s, int indent,
+ function_ref<bool(const DomItem &, const PathEls::PathComponent &, const DomItem &)> filter) const
{
- return subReferencePath(Path::key(keyName), referencedObject, loc);
+ visitEl([this, s, indent, filter](auto &&e) { e->dump(*this, s, indent, filter); });
}
-Subpath DomItem::subReferenceIndex(index_type i, Path referencedObject, const SourceLocation & loc) const
+FileWriter::Status
+DomItem::dump(const QString &path,
+ function_ref<bool(const DomItem &, const PathEls::PathComponent &, const DomItem &)> filter,
+ int nBackups, int indent, FileWriter *fw) const
{
- return subReferencePath(Path::index(i), referencedObject, loc);
+ 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;
}
-Subpath DomItem::subReferencePath(Path path, Path referencedObject, const SourceLocation & loc) const
+QString DomItem::toString() const
{
- if (domTypeIsOwningItem(internalKind()))
- return Subpath{path, DomItem(m_top, m_owner,
- Reference(referencedObject, path, loc))};
- else
- return Subpath{path, DomItem(m_top, m_owner,
- Reference(referencedObject, pathFromOwner().subPath(path), loc))};
+ return dumperToString([this](const Sink &s){ dump(s); });
}
-Subpath DomItem::toSubField(QStringView fieldName) const
+int DomItem::derivedFrom() const
{
- return Subpath{Path::field(fieldName), *this};
+ return std::visit([](auto &&ow) {
+ if constexpr (std::is_same_v<std::decay_t<decltype(ow)>, std::monostate>)
+ return 0;
+ else
+ return ow->derivedFrom();
+ }, m_owner);
}
-Subpath DomItem::toSubField(QString fieldName) const
+int DomItem::revision() const
{
- return Subpath{Path::field(fieldName), *this};
+ return std::visit([](auto &&ow) {
+ if constexpr (std::is_same_v<std::decay_t<decltype(ow)>, std::monostate>)
+ return -1;
+ else
+ return ow->revision();
+ }, m_owner);
}
-Subpath DomItem::toSubKey(QStringView keyName) const
+QDateTime DomItem::createdAt() const
{
- return Subpath{Path::key(keyName), *this};
+ return std::visit([](auto &&ow) {
+ if constexpr (std::is_same_v<std::decay_t<decltype(ow)>, std::monostate>)
+ return QDateTime::fromMSecsSinceEpoch(0, QTimeZone::UTC);
+ else
+ return ow->createdAt();
+ }, m_owner);
}
-Subpath DomItem::toSubKey(QString keyName) const
+QDateTime DomItem::frozenAt() const
{
- return Subpath{Path::key(keyName), *this};
+ return std::visit([](auto &&ow) {
+ if constexpr (std::is_same_v<std::decay_t<decltype(ow)>, std::monostate>)
+ return QDateTime::fromMSecsSinceEpoch(0, QTimeZone::UTC);
+ else
+ return ow->frozenAt();
+ }, m_owner);
}
-Subpath DomItem::toSubIndex(index_type i) const
+QDateTime DomItem::lastDataUpdateAt() const
{
- return Subpath{Path::index(i), *this};
+ return std::visit([](auto &&ow) {
+ if constexpr (std::is_same_v<std::decay_t<decltype(ow)>, std::monostate>)
+ return QDateTime::fromMSecsSinceEpoch(0, QTimeZone::UTC);
+ else
+ return ow->lastDataUpdateAt();
+ }, m_owner);
}
-Subpath DomItem::toSubPath(Path subPath) const
+void DomItem::addError(ErrorMessage &&msg) const
{
- return Subpath{subPath, *this};
+ std::visit([this, &msg](auto &&ow) {
+ if constexpr (std::is_same_v<std::decay_t<decltype(ow)>, std::monostate>)
+ defaultErrorHandler(msg.withItem(*this));
+ else
+ ow->addError(owner(), std::move(msg.withItem(*this)));
+ }, m_owner);
}
-Subpath DomItem::subList(const List &list) const
+ErrorHandler DomItem::errorHandler() const
{
- return Subpath{list.pathFromOwner().last(), DomItem(m_top, m_owner, list)};
+ // We need a copy here. Error handlers may be called when this is gone.
+ return [self = *this](const ErrorMessage &m) { self.addError(ErrorMessage(m)); };
}
-Subpath DomItem::subMap(const Map &map) const
+void DomItem::clearErrors(const ErrorGroups &groups, bool iterate) const
{
- return Subpath{map.pathFromOwner().last(), DomItem(m_top, m_owner, map)};
+ std::visit([&groups](auto &&ow) {
+ if constexpr (!std::is_same_v<std::decay_t<decltype(ow)>, std::monostate>)
+ ow->clearErrors(groups);
+ }, m_owner);
+
+ if (iterate) {
+ iterateSubOwners([groups](const DomItem &i){
+ i.clearErrors(groups, true);
+ return true;
+ });
+ }
}
-Subpath DomItem::subObjectWrap(const SimpleObjectWrap &obj) const
+bool DomItem::iterateErrors(
+ function_ref<bool(const DomItem &, const ErrorMessage &)> visitor, bool iterate,
+ Path inPath) const
{
- return Subpath{obj.pathFromOwner().last(), DomItem(m_top, m_owner, obj)};
+ if (!std::visit([this, visitor, inPath](auto &&el) {
+ if constexpr (std::is_same_v<std::decay_t<decltype(el)>, std::monostate>)
+ return true;
+ else
+ return el->iterateErrors(owner(), visitor, inPath);
+ }, m_owner)) {
+ return false;
+ }
+
+ if (iterate && !iterateSubOwners([inPath, visitor](const DomItem &i) {
+ return i.iterateErrors(visitor, true, inPath);
+ })) {
+ return false;
+ }
+
+ return true;
}
-DomItem::DomItem():
- DomItem(shared_ptr<DomTop>(), shared_ptr<OwningItem>(), nullptr)
+bool DomItem::iterateSubOwners(function_ref<bool(const DomItem &)> visitor) const
{
+ return std::visit([this, visitor](auto &&o) {
+ if constexpr (std::is_same_v<std::decay_t<decltype(o)>, std::monostate>)
+ return true;
+ else
+ return o->iterateSubOwners(owner(), visitor);
+ }, m_owner);
}
-DomItem::DomItem(std::shared_ptr<DomEnvironment> envPtr):
- DomItem(envPtr, envPtr, envPtr.get())
-{}
+bool DomItem::iterateDirectSubpaths(DirectVisitor v) const
+{
+ return visitEl(
+ [this, v](auto &&el) { return el->iterateDirectSubpaths(*this, v); });
+}
-DomItem::DomItem(std::shared_ptr<DomUniverse> universePtr):
- DomItem(universePtr, universePtr, universePtr.get())
-{}
+DomItem DomItem::subReferencesItem(const PathEls::PathComponent &c, const QList<Path> &paths) const
+{
+ return subListItem(
+ List::fromQList<Path>(pathFromOwner().appendComponent(c), paths,
+ [](const DomItem &list, const PathEls::PathComponent &p, const Path &el) {
+ return list.subReferenceItem(p, el);
+ }));
+}
-DomItem::DomItem(std::shared_ptr<DomTop> top, std::shared_ptr<OwningItem> owner, DomBase *base):
- m_top(top), m_owner(owner), m_base(base)
+DomItem DomItem::subReferenceItem(const PathEls::PathComponent &c, const Path &referencedObject) const
{
- if (m_base == nullptr || m_base->kind() == DomType::Empty) { // avoid null ptr, and allow only a single kind of Empty
- m_top.reset();
- m_owner.reset();
- m_base = nullptr;
+ if (domTypeIsOwningItem(internalKind())) {
+ return DomItem(m_top, m_owner, m_ownerPath, Reference(referencedObject, Path(c)));
+ } else {
+ return DomItem(m_top, m_owner, m_ownerPath,
+ Reference(referencedObject, pathFromOwner().appendComponent(c)));
}
}
-DomItem::DomItem(shared_ptr<DomTop> top, shared_ptr<OwningItem> owner, Map map):
- m_top(top), m_owner(owner), m_base(nullptr),
- inlineEl(map)
-{}
+shared_ptr<DomTop> DomItem::topPtr() const
+{
+ return std::visit([](auto &&el) -> shared_ptr<DomTop> {
+ if constexpr (std::is_same_v<std::decay_t<decltype(el)>, std::monostate>)
+ return {};
+ else
+ return el;
+ }, m_top);
+}
-DomItem::DomItem(shared_ptr<DomTop> top, shared_ptr<OwningItem> owner, List list):
- m_top(top), m_owner(owner), m_base(nullptr),
- inlineEl(list)
-{}
+shared_ptr<OwningItem> DomItem::owningItemPtr() const
+{
+ return std::visit([](auto &&el) -> shared_ptr<OwningItem> {
+ if constexpr (std::is_same_v<std::decay_t<decltype(el)>, std::monostate>)
+ return {};
+ else
+ return el;
+ }, m_owner);
+}
-DomItem::DomItem(shared_ptr<DomTop> top, shared_ptr<OwningItem> owner, ConstantData data):
- m_top(top), m_owner(owner), m_base(nullptr),
- inlineEl(data)
-{}
+/*!
+ \internal
+ Returns a pointer to the virtual base pointer to a DomBase.
+*/
+const DomBase *DomItem::base() const
+{
+ return visitEl([](auto &&el) -> const DomBase * { return el->domBase(); });
+}
-DomItem::DomItem(shared_ptr<DomTop> top, shared_ptr<OwningItem> owner, Reference reference):
- m_top(top), m_owner(owner), m_base(nullptr), inlineEl(reference)
-{}
+DomItem::DomItem(const std::shared_ptr<DomEnvironment> &envPtr):
+ DomItem(envPtr, envPtr, Path(), envPtr.get())
+{
+}
-DomItem::DomItem(std::shared_ptr<DomTop> top, std::shared_ptr<OwningItem> owner, SimpleObjectWrap wrapper):
- m_top(top), m_owner(owner), m_base(nullptr), inlineEl(wrapper)
-{}
+DomItem::DomItem(const std::shared_ptr<DomUniverse> &universePtr):
+ DomItem(universePtr, universePtr, Path(), universePtr.get())
+{
+}
+
+/*!
+\brief Creates a new document with the given code
+
+This is mostly useful for testing or loading a single code snippet without any dependency.
+The fileType should normally be QmlFile, but you might want to load a qmltypes file for
+example and interpret it as qmltypes file (not plain Qml), or as JsFile. In those case
+set the file type accordingly.
+*/
+DomItem DomItem::fromCode(const QString &code, DomType fileType)
+{
+ if (code.isEmpty())
+ return DomItem();
+ auto env =
+ DomEnvironment::create(QStringList(),
+ QQmlJS::Dom::DomEnvironment::Option::SingleThreaded
+ | QQmlJS::Dom::DomEnvironment::Option::NoDependencies);
+
+ DomItem tFile;
+
+ env->loadFile(
+ FileToLoad::fromMemory(env, QString(), code),
+ [&tFile](Path, const DomItem &, const DomItem &newIt) { tFile = newIt; },
+ std::make_optional(fileType));
+ env->loadPendingDependencies();
+ return tFile.fileObject();
+}
Empty::Empty()
{}
@@ -1231,7 +2589,7 @@ Path Empty::canonicalPath(const DomItem &) const
return Path();
}
-bool Empty::iterateDirectSubpaths(DomItem &, function<bool (Path, DomItem &)>)
+bool Empty::iterateDirectSubpaths(const DomItem &, DirectVisitor) const
{
return true;
}
@@ -1241,12 +2599,15 @@ DomItem Empty::containingObject(const DomItem &self) const
return self;
}
-void Empty::dump(const DomItem &, Sink s, int) const
+void Empty::dump(
+ const DomItem &, const Sink &s, int,
+ function_ref<bool(const DomItem &, const PathEls::PathComponent &, const DomItem &)>) const
{
s(u"null");
}
-Map::Map(Path pathFromOwner, Map::LookupFunction lookup, Keys keys, QString targetType)
+Map::Map(const Path &pathFromOwner, const Map::LookupFunction &lookup,
+ const Keys &keys, const QString &targetType)
: DomElement(pathFromOwner), m_lookup(lookup), m_keys(keys), m_targetType(targetType)
{}
@@ -1255,11 +2616,13 @@ quintptr Map::id() const
return quintptr(0);
}
-bool Map::iterateDirectSubpaths(DomItem &self, function<bool (Path, DomItem &)>visitor)
+bool Map::iterateDirectSubpaths(const DomItem &self, DirectVisitor visitor) const
{
- for (QString k:keys(self)) {
- DomItem el = key(self, k);
- if (!visitor(Path::key(k), el))
+ QSet<QString> ksSet = keys(self);
+ QStringList ksList = QStringList(ksSet.begin(), ksSet.end());
+ std::sort(ksList.begin(), ksList.end());
+ for (const QString &k : std::as_const(ksList)) {
+ if (!visitor(PathEls::Key(k), [&self, this, k]() { return key(self, k); }))
return false;
}
return true;
@@ -1270,12 +2633,14 @@ const QSet<QString> Map::keys(const DomItem &self) const
return m_keys(self);
}
-DomItem Map::key(const DomItem &self, QString name) const
+DomItem Map::key(const DomItem &self, const QString &name) const
{
return m_lookup(self, name);
}
-void DomBase::dump(const DomItem &self, Sink sink, int indent) const
+void DomBase::dump(
+ const DomItem &self, const Sink &sink, int indent,
+ function_ref<bool(const DomItem &, const PathEls::PathComponent &, const DomItem &)> filter) const
{
bool comma = false;
DomKind dK = self.domKind();
@@ -1314,6 +2679,9 @@ void DomBase::dump(const DomItem &self, Sink sink, int indent) const
case DomKind::Map:
sink(u"{");
break;
+ case DomKind::ScriptElement:
+ // nothing to print
+ break;
}
auto closeParens = qScopeGuard(
[dK, sink, indent]{
@@ -1334,55 +2702,65 @@ void DomBase::dump(const DomItem &self, Sink sink, int indent) const
sinkNewline(sink, indent);
sink(u"}");
break;
- }
- });
- index_type idx = 0;
- iterateDirectSubpathsConst(self, [&comma, &idx, dK, sink, indent, self](Path p, const DomItem &i) {
- if (comma)
- sink(u",");
- else
- comma = true;
- switch (p.headKind()) {
- case Path::Kind::Field:
- sinkNewline(sink, indent + 2);
- if (dK != DomKind::Object)
- sink(u"UNEXPECTED ENTRY ERROR:");
- sinkEscaped(sink, p.headName());
- sink(u":");
- break;
- case Path::Kind::Key:
- sinkNewline(sink, indent + 2);
- if (dK != DomKind::Map)
- sink(u"UNEXPECTED ENTRY ERROR:");
- sinkEscaped(sink, p.headName());
- sink(u":");
- break;
- case Path::Kind::Index:
- sinkNewline(sink, indent + 2);
- if (dK != DomKind::List)
- sink(u"UNEXPECTED ENTRY ERROR:");
- else if (idx++ != p.headIndex())
- sink(u"OUT OF ORDER ARRAY:");
+ case DomKind::ScriptElement:
+ // nothing to print
break;
- default:
- sinkNewline(sink, indent + 2);
- sink(u"UNEXPECTED PATH KIND ERROR (ignored)");
- break;
- }
- DomItem cObj=i.container();
- if (cObj == self) {
- i.dump(sink, indent + 2);
- } else {
- sink(uR"({ "~type~": "Reference", "immediate": true, "referredObjectPath":")");
- i.canonicalPath().dump([sink](QStringView s){ sinkEscaped(sink, s, EscapeOptions::NoOuterQuotes); });
- sink(u"\"}");
}
- return true;
});
+ index_type idx = 0;
+ self.iterateDirectSubpaths(
+ [&comma, &idx, dK, sink, indent, &self, filter](const PathEls::PathComponent &c,
+ function_ref<DomItem()> itemF) {
+ DomItem i = itemF();
+ if (!filter(self, c, i))
+ return true;
+ if (comma)
+ sink(u",");
+ else
+ comma = true;
+ switch (c.kind()) {
+ case Path::Kind::Field:
+ sinkNewline(sink, indent + 2);
+ if (dK != DomKind::Object)
+ sink(u"UNEXPECTED ENTRY ERROR:");
+ sinkEscaped(sink, c.name());
+ sink(u":");
+ break;
+ case Path::Kind::Key:
+ sinkNewline(sink, indent + 2);
+ if (dK != DomKind::Map)
+ sink(u"UNEXPECTED ENTRY ERROR:");
+ sinkEscaped(sink, c.name());
+ sink(u":");
+ break;
+ case Path::Kind::Index:
+ sinkNewline(sink, indent + 2);
+ if (dK != DomKind::List)
+ sink(u"UNEXPECTED ENTRY ERROR:");
+ else if (idx++ != c.index())
+ sink(u"OUT OF ORDER ARRAY:");
+ break;
+ default:
+ sinkNewline(sink, indent + 2);
+ sink(u"UNEXPECTED PATH KIND ERROR (ignored)");
+ break;
+ }
+ if (self.isCanonicalChild(i)) {
+ i.dump(sink, indent + 2, filter);
+ } else {
+ sink(uR"({ "~type~": "Reference", "immediate": true, "referredObjectPath":")");
+ i.canonicalPath().dump([sink](QStringView s) {
+ sinkEscaped(sink, s, EscapeOptions::NoOuterQuotes);
+ });
+ sink(u"\"}");
+ }
+ return true;
+ });
}
-List::List(Path pathFromOwner, List::LookupFunction lookup, List::Length length,
- List::IteratorFunction iterator, QString elType):
+List::List(const Path &pathFromOwner, const List::LookupFunction &lookup,
+ const List::Length &length, const List::IteratorFunction &iterator,
+ const QString &elType):
DomElement(pathFromOwner), m_lookup(lookup), m_length(length), m_iterator(iterator),
m_elType(elType)
{}
@@ -1392,33 +2770,40 @@ quintptr List::id() const
return quintptr(0);
}
-void List::dump(const DomItem &self, Sink sink, int indent) const
+void List::dump(
+ const DomItem &self, const Sink &sink, int indent,
+ function_ref<bool(const DomItem &, const PathEls::PathComponent &, const DomItem &)> filter) const
{
bool first = true;
sink(u"[");
- iterateDirectSubpathsConst(self, [indent, &first, sink](Path, const DomItem &item) {
- if (first)
- first = false;
- else
- sink(u",");
- sinkNewline(sink, indent + 2);
- item.dump(sink, indent + 2);
- return true;
- });
+ iterateDirectSubpaths(
+ self,
+ [&self, indent, &first, sink, filter](const PathEls::PathComponent &c,
+ function_ref<DomItem()> itemF) {
+ DomItem item = itemF();
+ if (!filter(self, c, item))
+ return true;
+ if (first)
+ first = false;
+ else
+ sink(u",");
+ sinkNewline(sink, indent + 2);
+ item.dump(sink, indent + 2, filter);
+ return true;
+ });
sink(u"]");
}
-bool List::iterateDirectSubpaths(DomItem &self, function<bool (Path, DomItem &)>visitor)
+bool List::iterateDirectSubpaths(const DomItem &self, DirectVisitor visitor) const
{
if (m_iterator) {
- return m_iterator(self, [visitor](index_type i, DomItem &item){
- return visitor(Path::empty().subIndex(i), item);
+ return m_iterator(self, [visitor](index_type i, function_ref<DomItem()> itemF) {
+ return visitor(PathEls::Index(i), itemF);
});
}
index_type len = indexes(self);
for (index_type i = 0; i < len; ++i) {
- DomItem idx = index(self, i);
- if (!visitor(Path::empty().subIndex(i), idx))
+ if (!visitor(PathEls::Index(i), [this, &self, i]() { return index(self, i); }))
return false;
}
return true;
@@ -1434,11 +2819,32 @@ DomItem List::index(const DomItem &self, index_type index) const
return m_lookup(self, index);
}
-DomElement::DomElement(Path pathFromOwner, const SourceLocation & loc):
- loc(loc), m_pathFromOwner(pathFromOwner)
+void List::writeOut(const DomItem &self, OutWriter &ow, bool compact) const
{
+ ow.writeRegion(LeftBracketRegion);
+ int baseIndent = ow.increaseIndent(1);
+ bool first = true;
+ iterateDirectSubpaths(
+ self,
+ [&ow, &first, compact](const PathEls::PathComponent &, function_ref<DomItem()> elF) {
+ if (first)
+ first = false;
+ else
+ ow.write(u", ", LineWriter::TextAddType::Extra);
+ if (!compact)
+ ow.ensureNewline(1);
+ DomItem el = elF();
+ el.writeOut(ow);
+ return true;
+ });
+ if (!compact && !first)
+ ow.newline();
+ ow.decreaseIndent(1, baseIndent);
+ ow.writeRegion(RightBracketRegion);
}
+DomElement::DomElement(const Path &pathFromOwner) : m_pathFromOwner(pathFromOwner) { }
+
Path DomElement::pathFromOwner(const DomItem &) const
{
Q_ASSERT(m_pathFromOwner && "uninitialized DomElement");
@@ -1448,7 +2854,7 @@ Path DomElement::pathFromOwner(const DomItem &) const
Path DomElement::canonicalPath(const DomItem &self) const
{
Q_ASSERT(m_pathFromOwner && "uninitialized DomElement");
- return self.owner().canonicalPath().subPath(m_pathFromOwner);
+ return self.owner().canonicalPath().path(m_pathFromOwner);
}
DomItem DomElement::containingObject(const DomItem &self) const
@@ -1457,19 +2863,41 @@ DomItem DomElement::containingObject(const DomItem &self) const
return DomBase::containingObject(self);
}
-void DomElement::updatePathFromOwner(Path newPath)
+void DomElement::updatePathFromOwner(const Path &newPath)
{
//if (!domTypeCanBeInline(kind()))
m_pathFromOwner = newPath;
}
-SourceLocation DomElement::location(const DomItem &) const
+bool Reference::shouldCache() const
{
- return loc;
+ for (const Path &p : referredObjectPath) {
+ switch (p.headKind()) {
+ case Path::Kind::Current:
+ switch (p.headCurrent()) {
+ case PathCurrent::Lookup:
+ case PathCurrent::LookupDynamic:
+ case PathCurrent::LookupStrict:
+ case PathCurrent::ObjChain:
+ case PathCurrent::ScopeChain:
+ return true;
+ default:
+ break;
+ }
+ break;
+ case Path::Kind::Empty:
+ case Path::Kind::Any:
+ case Path::Kind::Filter:
+ return true;
+ default:
+ break;
+ }
+ }
+ return false;
}
-Reference::Reference(Path referredObject, Path pathFromOwner, const SourceLocation & loc):
- DomElement(pathFromOwner, loc), referredObjectPath(referredObject)
+Reference::Reference(const Path &referredObject, const Path &pathFromOwner, const SourceLocation &)
+ : DomElement(pathFromOwner), referredObjectPath(referredObject)
{
}
@@ -1478,44 +2906,153 @@ quintptr Reference::id() const
return quintptr(0);
}
-
-bool Reference::iterateDirectSubpaths(DomItem &self, function<bool (Path, DomItem &)> visitor)
+bool Reference::iterateDirectSubpaths(const DomItem &self, DirectVisitor visitor) const
{
- if (!self.subDataField(Fields::referredObjectPath, referredObjectPath.toString()).visit(visitor))
- return false;
- DomItem res = get(self);
- if (!visitor(Path::field(Fields::get), res))
- return false;
- return true;
+ bool cont = true;
+ cont = cont && self.dvValueLazyField(visitor, Fields::referredObjectPath, [this]() {
+ return referredObjectPath.toString();
+ });
+ cont = cont
+ && self.dvItemField(visitor, Fields::get, [this, &self]() { return this->get(self); });
+ return cont;
}
DomItem Reference::field(const DomItem &self, QStringView name) const
{
if (Fields::referredObjectPath == name)
- return self.subDataField(Fields::referredObjectPath, referredObjectPath.toString()).item;
+ return self.subDataItemField(Fields::referredObjectPath, referredObjectPath.toString());
if (Fields::get == name)
return get(self);
return DomItem();
}
-const QList<QString> Reference::fields(const DomItem &) const
+QList<QString> Reference::fields(const DomItem &) const
{
return QList<QString>({QString::fromUtf16(Fields::referredObjectPath), QString::fromUtf16(Fields::get)});
}
-DomItem Reference::index(const DomItem &, index_type) const {
+DomItem Reference::index(const DomItem &, index_type) const
+{
return DomItem();
}
-DomItem Reference::key(const DomItem &, QString) const {
+DomItem Reference::key(const DomItem &, const QString &) const
+{
return DomItem();
}
-DomItem Reference::get(const DomItem &self) const
+DomItem Reference::get(const DomItem &self, const ErrorHandler &h, QList<Path> *visitedRefs) const
{
- if (!referredObjectPath)
- return DomItem();
- return self[referredObjectPath];
+ DomItem res;
+ if (referredObjectPath) {
+ DomItem env;
+ Path selfPath;
+ Path cachedPath;
+ if (shouldCache()) {
+ env = self.environment();
+ if (env) {
+ selfPath = self.canonicalPath();
+ RefCacheEntry cached = RefCacheEntry::forPath(self, selfPath);
+ switch (cached.cached) {
+ case RefCacheEntry::Cached::None:
+ break;
+ case RefCacheEntry::Cached::First:
+ case RefCacheEntry::Cached::All:
+ if (!cached.canonicalPaths.isEmpty())
+ cachedPath = cached.canonicalPaths.first();
+ else
+ return res;
+ break;
+ }
+ if (cachedPath) {
+ res = env.path(cachedPath);
+ if (!res)
+ qCWarning(refLog) << "referenceCache outdated, reference at " << selfPath
+ << " leads to invalid path " << cachedPath;
+ else
+ return res;
+ }
+ }
+ }
+ QList<Path> visitedRefsLocal;
+ self.resolve(
+ referredObjectPath,
+ [&res](Path, const DomItem &el) {
+ res = el;
+ return false;
+ },
+ h, ResolveOption::None, referredObjectPath,
+ (visitedRefs ? visitedRefs : &visitedRefsLocal));
+ if (env)
+ RefCacheEntry::addForPath(
+ env, selfPath, RefCacheEntry { RefCacheEntry::Cached::First, { cachedPath } });
+ }
+ return res;
+}
+
+QList<DomItem> Reference::getAll(
+ const DomItem &self, const ErrorHandler &h, QList<Path> *visitedRefs) const
+{
+ QList<DomItem> res;
+ if (referredObjectPath) {
+ DomItem env;
+ Path selfPath;
+ QList<Path> cachedPaths;
+ if (shouldCache()) {
+ selfPath = canonicalPath(self);
+ env = self.environment();
+ RefCacheEntry cached = RefCacheEntry::forPath(env, selfPath);
+ switch (cached.cached) {
+ case RefCacheEntry::Cached::None:
+ case RefCacheEntry::Cached::First:
+ break;
+ case RefCacheEntry::Cached::All:
+ cachedPaths += cached.canonicalPaths;
+ if (cachedPaths.isEmpty())
+ return res;
+ }
+ }
+ if (!cachedPaths.isEmpty()) {
+ bool outdated = false;
+ for (const Path &p : cachedPaths) {
+ DomItem newEl = env.path(p);
+ if (!newEl) {
+ outdated = true;
+ qCWarning(refLog) << "referenceCache outdated, reference at " << selfPath
+ << " leads to invalid path " << p;
+ break;
+ } else {
+ res.append(newEl);
+ }
+ }
+ if (outdated) {
+ res.clear();
+ } else {
+ return res;
+ }
+ }
+ self.resolve(
+ referredObjectPath,
+ [&res](Path, const DomItem &el) {
+ res.append(el);
+ return true;
+ },
+ h, ResolveOption::None, referredObjectPath, visitedRefs);
+ if (env) {
+ QList<Path> canonicalPaths;
+ for (const DomItem &i : res) {
+ if (i)
+ canonicalPaths.append(i.canonicalPath());
+ else
+ qCWarning(refLog)
+ << "getAll of reference at " << selfPath << " visits empty items.";
+ }
+ RefCacheEntry::addForPath(
+ env, selfPath,
+ RefCacheEntry { RefCacheEntry::Cached::All, std::move(canonicalPaths) });
+ }
+ }
+ return res;
}
/*!
@@ -1535,19 +3072,28 @@ OwningItems), Access to the rest is *not* controlled, it should either be by a s
*/
-OwningItem::OwningItem(int derivedFrom):
- m_derivedFrom(derivedFrom), m_revision(nextRevision()),
- m_createdAt(QDateTime::currentDateTime()), m_lastDataUpdateAt(m_createdAt), m_frozenAt(QDateTime::fromMSecsSinceEpoch(0))
+OwningItem::OwningItem(int derivedFrom)
+ : m_derivedFrom(derivedFrom),
+ m_revision(nextRevision()),
+ m_createdAt(QDateTime::currentDateTimeUtc()),
+ m_lastDataUpdateAt(m_createdAt),
+ m_frozenAt(QDateTime::fromMSecsSinceEpoch(0, QTimeZone::UTC))
{}
-OwningItem::OwningItem(int derivedFrom, QDateTime lastDataUpdateAt):
- m_derivedFrom(derivedFrom), m_revision(nextRevision()),
- m_createdAt(QDateTime::currentDateTime()), m_lastDataUpdateAt(lastDataUpdateAt), m_frozenAt(QDateTime::fromMSecsSinceEpoch(0))
+OwningItem::OwningItem(int derivedFrom, const QDateTime &lastDataUpdateAt)
+ : m_derivedFrom(derivedFrom),
+ m_revision(nextRevision()),
+ m_createdAt(QDateTime::currentDateTimeUtc()),
+ m_lastDataUpdateAt(lastDataUpdateAt),
+ m_frozenAt(QDateTime::fromMSecsSinceEpoch(0, QTimeZone::UTC))
{}
-OwningItem::OwningItem(const OwningItem &o):
- m_derivedFrom(o.revision()), m_revision(nextRevision()),
- m_createdAt(QDateTime::currentDateTime()), m_lastDataUpdateAt(o.lastDataUpdateAt()), m_frozenAt(QDateTime::fromMSecsSinceEpoch(0))
+OwningItem::OwningItem(const OwningItem &o)
+ : m_derivedFrom(o.revision()),
+ m_revision(nextRevision()),
+ m_createdAt(QDateTime::currentDateTimeUtc()),
+ m_lastDataUpdateAt(o.lastDataUpdateAt()),
+ m_frozenAt(QDateTime::fromMSecsSinceEpoch(0, QTimeZone::UTC))
{
QMultiMap<Path, ErrorMessage> my_errors;
{
@@ -1568,37 +3114,34 @@ int OwningItem::nextRevision()
return ++nextRev;
}
-bool OwningItem::iterateDirectSubpaths(DomItem &self, std::function<bool (Path, DomItem &)> visitor)
+bool OwningItem::iterateDirectSubpaths(const DomItem &self, DirectVisitor visitor) const
{
bool cont = true;
- QMultiMap<Path, ErrorMessage> myErrors = localErrors();
- cont = cont && self.subMap(
- Map(
- self.pathFromOwner().subField(Fields::errors),
- [myErrors](const DomItem &map, QString key) {
- auto it = myErrors.find(Path::fromString(key));
- if (it != myErrors.end())
- return map.subDataKey(
- key, it->toCbor(),
- ConstantData::Options::FirstMapIsFields, it->location).item;
- else
- return DomItem();
- }, [myErrors](const DomItem &) {
- QSet<QString> res;
- auto it = myErrors.keyBegin();
- auto end = myErrors.keyEnd();
- while (it != end)
- res.insert(it++->toString());
- return res;
- }, QLatin1String("ErrorMessages"))).visit(visitor);
+ cont = cont && self.dvItemField(visitor, Fields::errors, [&self, this]() {
+ QMultiMap<Path, ErrorMessage> myErrors = localErrors();
+ return self.subMapItem(Map(
+ self.pathFromOwner().field(Fields::errors),
+ [myErrors](const DomItem &map, const QString &key) {
+ auto it = myErrors.find(Path::fromString(key));
+ if (it != myErrors.end())
+ return map.subDataItem(PathEls::Key(key), it->toCbor(),
+ ConstantData::Options::FirstMapIsFields);
+ else
+ return DomItem();
+ },
+ [myErrors](const DomItem &) {
+ QSet<QString> res;
+ auto it = myErrors.keyBegin();
+ auto end = myErrors.keyEnd();
+ while (it != end)
+ res.insert(it++->toString());
+ return res;
+ },
+ QLatin1String("ErrorMessages")));
+ });
return cont;
}
-Path OwningItem::pathFromOwner(const DomItem &) const
-{
- return Path();
-}
-
DomItem OwningItem::containingObject(const DomItem &self) const
{
Source s = self.canonicalPath().split();
@@ -1628,7 +3171,7 @@ bool OwningItem::frozen() const
bool OwningItem::freeze()
{
if (!frozen()) {
- m_frozenAt = QDateTime::currentDateTime();
+ m_frozenAt = QDateTime::currentDateTimeUtc();
if (m_frozenAt <= m_createdAt)
m_frozenAt = m_createdAt.addSecs(1);
return true;
@@ -1657,23 +3200,21 @@ void OwningItem::refreshedDataAt(QDateTime tNew)
m_lastDataUpdateAt = tNew;
}
-void OwningItem::addError(const DomItem &, ErrorMessage msg)
+void OwningItem::addError(const DomItem &, ErrorMessage &&msg)
{
- addErrorLocal(msg);
+ addErrorLocal(std::move(msg));
}
-void OwningItem::addErrorLocal(ErrorMessage msg)
+void OwningItem::addErrorLocal(ErrorMessage &&msg)
{
QMutexLocker l(mutex());
- auto it = m_errors.constFind(msg.path);
- while (it != m_errors.constEnd() && it->path == msg.path) {
- if (*it++ == msg)
- return;
- }
- m_errors.insert(msg.path, msg);
+ quint32 &c = m_errorsCounts[msg];
+ c += 1;
+ if (c == 1)
+ m_errors.insert(msg.path, msg);
}
-void OwningItem::clearErrors(ErrorGroups groups)
+void OwningItem::clearErrors(const ErrorGroups &groups)
{
QMutexLocker l(mutex());
auto it = m_errors.begin();
@@ -1685,8 +3226,9 @@ void OwningItem::clearErrors(ErrorGroups groups)
}
}
-bool OwningItem::iterateErrors(const DomItem &self, function<bool (DomItem, ErrorMessage)> visitor,
- Path inPath)
+bool OwningItem::iterateErrors(
+ const DomItem &self, function_ref<bool(const DomItem &, const ErrorMessage &)> visitor,
+ const Path &inPath)
{
QMultiMap<Path, ErrorMessage> myErrors;
{
@@ -1696,147 +3238,405 @@ bool OwningItem::iterateErrors(const DomItem &self, function<bool (DomItem, Erro
auto it = myErrors.lowerBound(inPath);
auto end = myErrors.end();
while (it != end && it.key().mid(0, inPath.length()) == inPath) {
- if (!visitor(self, *it))
+ if (!visitor(self, *it++))
return false;
}
return true;
}
-bool OwningItem::iterateSubOwners(const DomItem &self, function<bool (const DomItem &)>visitor)
+bool OwningItem::iterateSubOwners(const DomItem &self, function_ref<bool(const DomItem &owner)> visitor)
{
- return iterateDirectSubpathsConst(self,[self, visitor](Path, const DomItem &i) {
- if (i.owningItemPtr() != self.owningItemPtr() && i.container().id() == self.id())
- return visitor(i);
+ return self.iterateDirectSubpaths(
+ [&self, visitor](const PathEls::PathComponent &, function_ref<DomItem()> iF) {
+ DomItem i = iF();
+ if (i.owningItemPtr() != self.owningItemPtr()) {
+ DomItem container = i.container();
+ if (container.id() == self.id())
+ return visitor(i);
+ }
+ return true;
+ });
+}
+
+bool operator==(const DomItem &o1, const DomItem &o2)
+{
+ if (o1.m_kind != o2.m_kind)
+ return false;
+ return o1.visitEl([&o1, &o2](auto &&el1) {
+ auto &&el2 = std::get<std::decay_t<decltype(el1)>>(o2.m_element);
+ auto id1 = el1->id();
+ auto id2 = el2->id();
+ if (id1 != id2)
+ return false;
+ if (id1 != quintptr(0))
+ return true;
+ if (o1.m_owner != o2.m_owner)
+ return false;
+ Path p1 = el1->pathFromOwner(o1);
+ Path p2 = el2->pathFromOwner(o2);
+ if (p1 != p2)
+ return false;
return true;
});
}
-GenericObject GenericObject::copy() const
+ErrorHandler MutableDomItem::errorHandler()
{
- QMap<QString, GenericObject> newObjs;
- auto objs = subObjects;
- auto itO = objs.cbegin();
- auto endO = objs.cend();
- while (itO != endO) {
- newObjs.insert(itO.key(),itO->copy());
- ++itO;
- }
- return GenericObject(pathFromOwner(), loc, newObjs, subValues);
+ MutableDomItem self;
+ return [&self](const ErrorMessage &m) { self.addError(ErrorMessage(m)); };
}
-std::pair<QString, GenericObject> GenericObject::asStringPair() const
+MutableDomItem MutableDomItem::addPrototypePath(const Path &prototypePath)
{
- return std::make_pair(pathFromOwner().last().headName(), *this);
+ if (QmlObject *el = mutableAs<QmlObject>()) {
+ return path(el->addPrototypePath(prototypePath));
+ } else {
+ Q_ASSERT(false && "setPrototypePath on non qml scope");
+ return {};
+ }
}
-bool GenericObject::iterateDirectSubpaths(DomItem &self, std::function<bool (Path, DomItem &)> visitor)
+MutableDomItem MutableDomItem::setNextScopePath(const Path &nextScopePath)
{
- bool cont = true;
- auto itV = subValues.begin();
- auto endV = subValues.end();
- while (itV != endV) {
- cont = cont && self.subDataField(itV.key(), *itV).visit(visitor);
- ++itV;
- }
- auto itO = subObjects.begin();
- auto endO = subObjects.end();
- while (itO != endO) {
- cont = cont && self.copy(&(*itO)).toSubField(itO.key()).visit(visitor);
- ++itO;
+ if (QmlObject *el = mutableAs<QmlObject>()) {
+ el->setNextScopePath(nextScopePath);
+ return field(Fields::nextScope);
+ } else {
+ Q_ASSERT(false && "setNextScopePath on non qml scope");
+ return {};
}
- return cont;
}
-std::shared_ptr<OwningItem> GenericOwner::doCopy(const DomItem &)
+MutableDomItem MutableDomItem::setPropertyDefs(QMultiMap<QString, PropertyDefinition> propertyDefs)
{
- return std::make_shared<GenericOwner>(*this);
+ if (QmlObject *el = mutableAs<QmlObject>()) {
+ el->setPropertyDefs(propertyDefs);
+ return field(Fields::propertyDefs);
+ } else {
+ Q_ASSERT(false && "setPropertyDefs on non qml scope");
+ return {};
+ }
}
-GenericOwner::GenericOwner(const GenericOwner &o):
- OwningItem(o), pathFromTop(o.pathFromTop),
- subValues(o.subValues)
+MutableDomItem MutableDomItem::setBindings(QMultiMap<QString, Binding> bindings)
{
- auto objs = o.subObjects;
- auto itO = objs.cbegin();
- auto endO = objs.cend();
- while (itO != endO) {
- subObjects.insert(itO.key(),itO->copy());
- ++itO;
+ if (QmlObject *el = mutableAs<QmlObject>()) {
+ el->setBindings(bindings);
+ return field(Fields::bindings);
+ } else {
+ Q_ASSERT(false && "setBindings on non qml scope");
+ return {};
}
}
-std::shared_ptr<GenericOwner> GenericOwner::makeCopy(const DomItem &self)
+MutableDomItem MutableDomItem::setMethods(QMultiMap<QString, MethodInfo> functionDefs)
{
- return std::static_pointer_cast<GenericOwner>(doCopy(self));
+ if (QmlObject *el = mutableAs<QmlObject>())
+ el->setMethods(functionDefs);
+ else
+ Q_ASSERT(false && "setMethods on non qml scope");
+ return {};
+}
+
+MutableDomItem MutableDomItem::setChildren(const QList<QmlObject> &children)
+{
+ if (QmlObject *el = mutableAs<QmlObject>()) {
+ el->setChildren(children);
+ return field(Fields::children);
+ } else
+ Q_ASSERT(false && "setChildren on non qml scope");
+ return {};
+}
+
+MutableDomItem MutableDomItem::setAnnotations(const QList<QmlObject> &annotations)
+{
+ if (QmlObject *el = mutableAs<QmlObject>())
+ el->setAnnotations(annotations);
+ else if (Binding *el = mutableAs<Binding>()) {
+ el->setAnnotations(annotations);
+ el->updatePathFromOwner(pathFromOwner());
+ } else if (PropertyDefinition *el = mutableAs<PropertyDefinition>()) {
+ el->annotations = annotations;
+ el->updatePathFromOwner(pathFromOwner());
+ } else if (MethodInfo *el = mutableAs<MethodInfo>()) {
+ el->annotations = annotations;
+ el->updatePathFromOwner(pathFromOwner());
+ } else if (EnumDecl *el = mutableAs<EnumDecl>()) {
+ el->setAnnotations(annotations);
+ el->updatePathFromOwner(pathFromOwner());
+ } else if (!annotations.isEmpty()) {
+ Q_ASSERT(false && "setAnnotations on element not supporting them");
+ }
+ return field(Fields::annotations);
}
-
-Path GenericOwner::canonicalPath(const DomItem &) const
+MutableDomItem MutableDomItem::setScript(const std::shared_ptr<ScriptExpression> &exp)
{
- return pathFromTop;
+ switch (internalKind()) {
+ case DomType::Binding:
+ if (Binding *b = mutableAs<Binding>()) {
+ b->setValue(std::make_unique<BindingValue>(exp));
+ return field(Fields::value);
+ }
+ break;
+ case DomType::MethodInfo:
+ if (MethodInfo *m = mutableAs<MethodInfo>()) {
+ m->body = exp;
+ return field(Fields::body);
+ }
+ break;
+ case DomType::MethodParameter:
+ if (MethodParameter *p = mutableAs<MethodParameter>()) {
+ if (exp->expressionType() == ScriptExpression::ExpressionType::ArgInitializer) {
+ p->defaultValue = exp;
+ return field(Fields::defaultValue);
+ }
+ if (exp->expressionType() == ScriptExpression::ExpressionType::ArgumentStructure) {
+ p->value = exp;
+ return field(Fields::value);
+ }
+ }
+ break;
+ case DomType::ScriptExpression:
+ return container().setScript(exp);
+ default:
+ qCWarning(domLog) << "setScript called on" << internalKindStr();
+ Q_ASSERT_X(false, "setScript",
+ "setScript supported only on Binding, MethodInfo, MethodParameter, and "
+ "ScriptExpression contained in them");
+ }
+ return MutableDomItem();
}
-bool GenericOwner::iterateDirectSubpaths(DomItem &self, std::function<bool (Path, DomItem &)> visitor)
+MutableDomItem MutableDomItem::setCode(const QString &code)
{
- bool cont = true;
- auto itV = subValues.begin();
- auto endV = subValues.end();
- while (itV != endV) {
- cont = cont && self.subDataField(itV.key(), *itV).visit(visitor);
- ++itV;
- }
- auto itO = subObjects.begin();
- auto endO = subObjects.end();
- while (itO != endO) {
- cont = cont && self.copy(&(*itO)).toSubField(itO.key()).visit(visitor);
- ++itO;
+ DomItem it = item();
+ switch (it.internalKind()) {
+ case DomType::Binding:
+ if (Binding *b = mutableAs<Binding>()) {
+ auto exp = std::make_shared<ScriptExpression>(
+ code, ScriptExpression::ExpressionType::BindingExpression);
+ b->setValue(std::make_unique<BindingValue>(exp));
+ return field(Fields::value);
+ }
+ break;
+ case DomType::MethodInfo:
+ if (MethodInfo *m = mutableAs<MethodInfo>()) {
+ QString pre = m->preCode(it);
+ QString post = m->preCode(it);
+ m->body = std::make_shared<ScriptExpression>(
+ code, ScriptExpression::ExpressionType::FunctionBody, 0, pre, post);
+ return field(Fields::body);
+ }
+ break;
+ case DomType::MethodParameter:
+ if (MethodParameter *p = mutableAs<MethodParameter>()) {
+ p->defaultValue = std::make_shared<ScriptExpression>(
+ code, ScriptExpression::ExpressionType::ArgInitializer);
+ return field(Fields::defaultValue);
+ }
+ break;
+ case DomType::ScriptExpression:
+ if (std::shared_ptr<ScriptExpression> exp = ownerAs<ScriptExpression>()) {
+ std::shared_ptr<ScriptExpression> newExp = exp->copyWithUpdatedCode(it, code);
+ return container().setScript(newExp);
+ }
+ break;
+ default:
+ qCWarning(domLog) << "setCode called on" << internalKindStr();
+ Q_ASSERT_X(
+ false, "setCode",
+ "setCode supported only on Binding, MethodInfo, MethodParameter, ScriptExpression");
}
- return cont;
+ return MutableDomItem();
}
-bool operator ==(const DomItem &o1, const DomItem &o2) {
- if (o1.base() == o2.base())
- return true;
- quintptr i1 = o1.id();
- quintptr i2 = o2.id();
- if (i1 != i2)
- return false;
- if (i1 != quintptr(0))
- return true;
- Path p1 = o1.pathFromOwner();
- Path p2 = o2.pathFromOwner();
- if (p1 != p2)
- return false;
- return o1.owningItemPtr() == o2.owningItemPtr();
+MutableDomItem MutableDomItem::addPropertyDef(
+ const PropertyDefinition &propertyDef, AddOption option)
+{
+ if (QmlObject *el = mutableAs<QmlObject>())
+ return el->addPropertyDef(*this, propertyDef, option);
+ else
+ Q_ASSERT(false && "addPropertyDef on non qml scope");
+ return MutableDomItem();
+}
+
+MutableDomItem MutableDomItem::addBinding(Binding binding, AddOption option)
+{
+ if (QmlObject *el = mutableAs<QmlObject>())
+ return el->addBinding(*this, binding, option);
+ else
+ Q_ASSERT(false && "addBinding on non qml scope");
+ return MutableDomItem();
}
-QString MutableDomItem::name() const
+MutableDomItem MutableDomItem::addMethod(const MethodInfo &functionDef, AddOption option)
{
- return base().name();
+ if (QmlObject *el = mutableAs<QmlObject>())
+ return el->addMethod(*this, functionDef, option);
+ else
+ Q_ASSERT(false && "addMethod on non qml scope");
+ return MutableDomItem();
}
-ErrorHandler MutableDomItem::errorHandler() const
+MutableDomItem MutableDomItem::addChild(QmlObject child)
{
- MutableDomItem self;
- return [self](ErrorMessage m){
- self.addError(m);
- };
+ if (QmlObject *el = mutableAs<QmlObject>()) {
+ return el->addChild(*this, child);
+ } else if (QmlComponent *el = mutableAs<QmlComponent>()) {
+ Path p = el->addObject(child);
+ return owner().path(p); // convenience: treat component objects as children
+ } else {
+ Q_ASSERT(false && "addChild on non qml scope");
+ }
+ return MutableDomItem();
+}
+
+MutableDomItem MutableDomItem::addAnnotation(QmlObject annotation)
+{
+ Path res;
+ switch (internalKind()) {
+ case DomType::QmlObject: {
+ QmlObject *el = mutableAs<QmlObject>();
+ res = el->addAnnotation(annotation);
+ } break;
+ case DomType::Binding: {
+ Binding *el = mutableAs<Binding>();
+
+ res = el->addAnnotation(m_pathFromOwner, annotation);
+ } break;
+ case DomType::PropertyDefinition: {
+ PropertyDefinition *el = mutableAs<PropertyDefinition>();
+ res = el->addAnnotation(m_pathFromOwner, annotation);
+ } break;
+ case DomType::MethodInfo: {
+ MethodInfo *el = mutableAs<MethodInfo>();
+ res = el->addAnnotation(m_pathFromOwner, annotation);
+ } break;
+ case DomType::Id: {
+ Id *el = mutableAs<Id>();
+ res = el->addAnnotation(m_pathFromOwner, annotation);
+ } break;
+ default:
+ Q_ASSERT(false && "addAnnotation on element not supporting them");
+ }
+ return MutableDomItem(owner().item(), res);
+}
+
+MutableDomItem MutableDomItem::addPreComment(const Comment &comment, FileLocationRegion region)
+{
+ index_type idx;
+ MutableDomItem rC = field(Fields::comments);
+ if (auto rcPtr = rC.mutableAs<RegionComments>()) {
+ auto commentedElement = rcPtr->regionComments()[region];
+ idx = commentedElement.preComments().size();
+ commentedElement.addComment(comment);
+ MutableDomItem res = path(Path::Field(Fields::comments)
+ .field(Fields::regionComments)
+ .key(fileLocationRegionName(region))
+ .field(Fields::preComments)
+ .index(idx));
+ Q_ASSERT(res);
+ return res;
+ }
+ return MutableDomItem();
+}
+
+MutableDomItem MutableDomItem::addPostComment(const Comment &comment, FileLocationRegion region)
+{
+ index_type idx;
+ MutableDomItem rC = field(Fields::comments);
+ if (auto rcPtr = rC.mutableAs<RegionComments>()) {
+ auto commentedElement = rcPtr->regionComments()[region];
+ idx = commentedElement.postComments().size();
+ commentedElement.addComment(comment);
+ MutableDomItem res = path(Path::Field(Fields::comments)
+ .field(Fields::regionComments)
+ .key(fileLocationRegionName(region))
+ .field(Fields::postComments)
+ .index(idx));
+ Q_ASSERT(res);
+ return res;
+ }
+ return MutableDomItem();
}
QDebug operator<<(QDebug debug, const DomItem &c)
{
- dumperToQDebug([&c](Sink s) {
- c.dump(s);
- }, debug);
+ dumperToQDebug([&c](const Sink &s) { c.dump(s); }, debug);
return debug;
}
QDebug operator<<(QDebug debug, const MutableDomItem &c)
{
- return debug.noquote().nospace() << "MutableDomItem(" << domTypeToString(c.internalKind())
- << ", " << c.canonicalPath().toString() << ")";
+ MutableDomItem cc(c);
+ return debug.noquote().nospace() << "MutableDomItem(" << domTypeToString(cc.internalKind())
+ << ", " << cc.canonicalPath().toString() << ")";
+}
+
+bool ListPBase::iterateDirectSubpaths(const DomItem &self, DirectVisitor v) const
+{
+ index_type len = index_type(m_pList.size());
+ for (index_type i = 0; i < len; ++i) {
+ if (!v(PathEls::Index(i), [this, &self, i] { return this->index(self, i); }))
+ return false;
+ }
+ return true;
+}
+
+void ListPBase::writeOut(const DomItem &self, OutWriter &ow, bool compact) const
+{
+ ow.writeRegion(LeftBracketRegion);
+ int baseIndent = ow.increaseIndent(1);
+ bool first = true;
+ index_type len = index_type(m_pList.size());
+ for (index_type i = 0; i < len; ++i) {
+ if (first)
+ first = false;
+ else
+ ow.write(u", ", LineWriter::TextAddType::Extra);
+ if (!compact)
+ ow.ensureNewline(1);
+ DomItem el = index(self, i);
+ el.writeOut(ow);
+ }
+ if (!compact && !first)
+ ow.newline();
+ ow.decreaseIndent(1, baseIndent);
+ ow.writeRegion(RightBracketRegion);
+}
+
+QQmlJSScope::ConstPtr ScriptElement::semanticScope()
+{
+ return m_scope;
+}
+void ScriptElement::setSemanticScope(const QQmlJSScope::ConstPtr &scope)
+{
+ m_scope = scope;
+}
+
+/*!
+ \internal
+ \brief Returns a pointer to the virtual base for virtual method calls.
+
+ A helper to call virtual methods without having to call std::visit(...).
+ */
+ScriptElement::PointerType<ScriptElement> ScriptElementVariant::base() const
+{
+ if (!m_data)
+ return nullptr;
+
+ return std::visit(
+ [](auto &&e) {
+ // std::reinterpret_pointer_cast does not exist on qnx it seems...
+ return std::shared_ptr<ScriptElement>(
+ e, static_cast<ScriptElement *>(e.get()));
+ },
+ *m_data);
}
} // end namespace Dom
} // end namespace QQmlJS
QT_END_NAMESPACE
+
+#include "moc_qqmldomitem_p.cpp"
diff --git a/src/qmldom/qqmldomitem_p.h b/src/qmldom/qqmldomitem_p.h
index 92535b87ac..50775a1db2 100644
--- a/src/qmldom/qqmldomitem_p.h
+++ b/src/qmldom/qqmldomitem_p.h
@@ -1,40 +1,6 @@
-/****************************************************************************
-**
-** 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$
-**/
+// Copyright (C) 2020 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+
#ifndef QMLDOMITEM_H
#define QMLDOMITEM_H
@@ -51,24 +17,36 @@
#include "qqmldom_global.h"
#include "qqmldom_fwd_p.h"
+#include "qqmldom_utils_p.h"
#include "qqmldomconstants_p.h"
#include "qqmldomstringdumper_p.h"
#include "qqmldompath_p.h"
#include "qqmldomerrormessage_p.h"
+#include "qqmldomfunctionref_p.h"
+#include "qqmldomfilewriter_p.h"
+#include "qqmldomlinewriter_p.h"
+#include "qqmldomfieldfilter_p.h"
#include <QtCore/QMap>
#include <QtCore/QMultiMap>
+#include <QtCore/QSet>
#include <QtCore/QString>
#include <QtCore/QStringView>
#include <QtCore/QDebug>
#include <QtCore/QDateTime>
#include <QtCore/QMutex>
#include <QtCore/QCborValue>
+#include <QtCore/QTimeZone>
#include <QtQml/private/qqmljssourcelocation_p.h>
+#include <QtQmlCompiler/private/qqmljsscope_p.h>
#include <memory>
#include <typeinfo>
#include <utility>
+#include <type_traits>
+#include <variant>
+#include <optional>
+#include <cstddef>
QT_BEGIN_NAMESPACE
@@ -78,50 +56,193 @@ namespace Dom {
class Path;
-bool domTypeIsObjWrap(DomType k);
-bool domTypeIsDomElement(DomType);
-bool domTypeIsOwningItem(DomType);
-bool domTypeIsExternalItem(DomType k);
-bool domTypeIsTopItem(DomType k);
-bool domTypeIsContainer(DomType k);
-bool domTypeCanBeInline(DomType k);
+Q_DECLARE_LOGGING_CATEGORY(writeOutLog);
+
+constexpr bool domTypeIsObjWrap(DomType k);
+constexpr bool domTypeIsValueWrap(DomType k);
+constexpr bool domTypeIsDomElement(DomType);
+constexpr bool domTypeIsOwningItem(DomType);
+constexpr bool domTypeIsUnattachedOwningItem(DomType);
+constexpr bool domTypeIsScriptElement(DomType);
+QMLDOM_EXPORT bool domTypeIsExternalItem(DomType k);
+QMLDOM_EXPORT bool domTypeIsTopItem(DomType k);
+QMLDOM_EXPORT bool domTypeIsContainer(DomType k);
+constexpr bool domTypeCanBeInline(DomType k)
+{
+ switch (k) {
+ case DomType::Empty:
+ case DomType::Map:
+ case DomType::List:
+ case DomType::ListP:
+ case DomType::ConstantData:
+ case DomType::SimpleObjectWrap:
+ case DomType::ScriptElementWrap:
+ case DomType::Reference:
+ return true;
+ default:
+ return false;
+ }
+}
+QMLDOM_EXPORT bool domTypeIsScope(DomType k);
QMLDOM_EXPORT QMap<DomType,QString> domTypeToStringMap();
QMLDOM_EXPORT QString domTypeToString(DomType k);
+QMLDOM_EXPORT QMap<DomKind, QString> domKindToStringMap();
+QMLDOM_EXPORT QString domKindToString(DomKind k);
+
+inline bool noFilter(const DomItem &, const PathEls::PathComponent &, const DomItem &)
+{
+ return true;
+}
+
+using DirectVisitor = function_ref<bool(const PathEls::PathComponent &, function_ref<DomItem()>)>;
+// using DirectVisitor = function_ref<bool(Path, const DomItem &)>;
+
+namespace {
+template<typename T>
+struct IsMultiMap : std::false_type
+{
+};
+
+template<typename Key, typename T>
+struct IsMultiMap<QMultiMap<Key, T>> : std::true_type
+{
+};
+
+template<typename T>
+struct IsMap : std::false_type
+{
+};
+
+template<typename Key, typename T>
+struct IsMap<QMap<Key, T>> : std::true_type
+{
+};
+
+template<typename... Ts>
+using void_t = void;
+
+template<typename T, typename = void>
+struct IsDomObject : std::false_type
+{
+};
+
+template<typename T>
+struct IsDomObject<T, void_t<decltype(T::kindValue)>> : std::true_type
+{
+};
+
+template<typename T, typename = void>
+struct IsInlineDom : std::false_type
+{
+};
+
+template<typename T>
+struct IsInlineDom<T, void_t<decltype(T::kindValue)>>
+ : std::integral_constant<bool, domTypeCanBeInline(T::kindValue)>
+{
+};
+
+template<typename T>
+struct IsInlineDom<T *, void_t<decltype(T::kindValue)>> : std::true_type
+{
+};
+
+template<typename T>
+struct IsInlineDom<std::shared_ptr<T>, void_t<decltype(T::kindValue)>> : std::true_type
+{
+};
+
+template<typename T>
+struct IsSharedPointerToDomObject : std::false_type
+{
+};
+
+template<typename T>
+struct IsSharedPointerToDomObject<std::shared_ptr<T>> : IsDomObject<T>
+{
+};
+
+template<typename T, typename = void>
+struct IsList : std::false_type
+{
+};
+
+template<typename T>
+struct IsList<T, void_t<typename T::value_type>> : std::true_type
+{
+};
+
+}
+
+template<typename T>
+union SubclassStorage {
+ int i;
+ T lp;
+
+ // TODO: these are extremely nasty. What is this int doing in here?
+ T *data() { return reinterpret_cast<T *>(this); }
+ const T *data() const { return reinterpret_cast<const T *>(this); }
+
+ SubclassStorage() { }
+ SubclassStorage(T &&el) { el.moveTo(data()); }
+ SubclassStorage(const T *el) { el->copyTo(data()); }
+ SubclassStorage(const SubclassStorage &o) : SubclassStorage(o.data()) { }
+ SubclassStorage(const SubclassStorage &&o) : SubclassStorage(o.data()) { }
+ SubclassStorage &operator=(const SubclassStorage &o)
+ {
+ data()->~T();
+ o.data()->copyTo(data());
+ return *this;
+ }
+ ~SubclassStorage() { data()->~T(); }
+};
-class QMLDOM_EXPORT DomBase{
+class QMLDOM_EXPORT DomBase
+{
public:
+ using FilterT = function_ref<bool(const DomItem &, const PathEls::PathComponent &, const DomItem &)>;
+
virtual ~DomBase() = default;
+ DomBase *domBase() { return this; }
+ const DomBase *domBase() const { return this; }
+
// minimal overload set:
virtual DomType kind() const = 0;
virtual DomKind domKind() const;
virtual Path pathFromOwner(const DomItem &self) const = 0;
virtual Path canonicalPath(const DomItem &self) const = 0;
- virtual bool iterateDirectSubpaths(DomItem &self, std::function<bool(Path, DomItem &)>) = 0; // iterates the *direct* subpaths, returns false if a quick end was requested
- bool iterateDirectSubpathsConst(const DomItem &self, std::function<bool(Path, const DomItem &)>) const; // iterates the *direct* subpaths, returns false if a quick end was requested
+ virtual bool
+ iterateDirectSubpaths(const DomItem &self,
+ DirectVisitor visitor) const = 0; // iterates the *direct* subpaths, returns
+ // false if a quick end was requested
+
+ bool iterateDirectSubpathsConst(const DomItem &self, DirectVisitor)
+ const; // iterates the *direct* subpaths, returns false if a quick end was requested
- virtual DomItem containingObject(const DomItem &self) const; // the DomItem corresponding to the canonicalSource source
- virtual void dump(const DomItem &, Sink sink, int indent) const;
+ virtual DomItem containingObject(
+ const DomItem &self) const; // the DomItem corresponding to the canonicalSource source
+ virtual void dump(const DomItem &, const Sink &sink, int indent, FilterT filter) const;
virtual quintptr id() const;
- virtual QString typeName() const;
+ QString typeName() const;
- virtual QList<QString> const fields(const DomItem &self) const;
+ virtual QList<QString> fields(const DomItem &self) const;
virtual DomItem field(const DomItem &self, QStringView name) const;
virtual index_type indexes(const DomItem &self) const;
virtual DomItem index(const DomItem &self, index_type index) const;
virtual QSet<QString> const keys(const DomItem &self) const;
- virtual DomItem key(const DomItem &self, QString name) const;
+ virtual DomItem key(const DomItem &self, const QString &name) const;
virtual QString canonicalFilePath(const DomItem &self) const;
- virtual SourceLocation location(const DomItem &self) const;
+
+ virtual void writeOut(const DomItem &self, OutWriter &lw) const;
virtual QCborValue value() const {
return QCborValue();
}
-
};
inline DomKind kind2domKind(DomType k)
@@ -130,6 +251,7 @@ inline DomKind kind2domKind(DomType k)
case DomType::Empty:
return DomKind::Empty;
case DomType::List:
+ case DomType::ListP:
return DomKind::List;
case DomType::Map:
return DomKind::Map;
@@ -140,80 +262,128 @@ inline DomKind kind2domKind(DomType k)
}
}
-class QMLDOM_EXPORT Empty: public DomBase {
+class QMLDOM_EXPORT Empty final : public DomBase
+{
public:
constexpr static DomType kindValue = DomType::Empty;
DomType kind() const override { return kindValue; }
+ Empty *operator->() { return this; }
+ const Empty *operator->() const { return this; }
+ Empty &operator*() { return *this; }
+ const Empty &operator*() const { return *this; }
+
Empty();
+ quintptr id() const override { return ~quintptr(0); }
Path pathFromOwner(const DomItem &self) const override;
Path canonicalPath(const DomItem &self) const override;
DomItem containingObject(const DomItem &self) const override;
- bool iterateDirectSubpaths(DomItem &self, std::function<bool (Path, DomItem &)>) override;
- void dump(const DomItem &, Sink s, int indent) const override;
+ bool iterateDirectSubpaths(const DomItem &self, DirectVisitor) const override;
+ void dump(const DomItem &, const Sink &s, int indent,
+ function_ref<bool(const DomItem &, const PathEls::PathComponent &, const DomItem &)> filter)
+ const override;
};
class QMLDOM_EXPORT DomElement: public DomBase {
protected:
DomElement& operator=(const DomElement&) = default;
public:
- DomElement(Path pathFromOwner = Path(), const SourceLocation & loc = SourceLocation());
+ DomElement(const Path &pathFromOwner = Path());
DomElement(const DomElement &o) = default;
Path pathFromOwner(const DomItem &self) const override;
Path pathFromOwner() const { return m_pathFromOwner; }
Path canonicalPath(const DomItem &self) const override;
DomItem containingObject(const DomItem &self) const override;
- virtual void updatePathFromOwner(Path newPath);
- SourceLocation location(const DomItem &self) const override;
+ virtual void updatePathFromOwner(const Path &newPath);
- SourceLocation loc;
private:
Path m_pathFromOwner;
};
-class QMLDOM_EXPORT Map: public DomElement {
+class QMLDOM_EXPORT Map final : public DomElement
+{
public:
constexpr static DomType kindValue = DomType::Map;
DomType kind() const override { return kindValue; }
- using LookupFunction = std::function<DomItem (const DomItem&, QString)>;
- using Keys = std::function<QSet<QString> (const DomItem &)>;
- Map(Path pathFromOwner, LookupFunction lookup, Keys keys, QString targetType);
+ Map *operator->() { return this; }
+ const Map *operator->() const { return this; }
+ Map &operator*() { return *this; }
+ const Map &operator*() const { return *this; }
+
+ using LookupFunction = std::function<DomItem(const DomItem &, QString)>;
+ using Keys = std::function<QSet<QString>(const DomItem &)>;
+ Map(const Path &pathFromOwner, const LookupFunction &lookup,
+ const Keys &keys, const QString &targetType);
quintptr id() const override;
- bool iterateDirectSubpaths(DomItem &self, std::function<bool(Path, DomItem &)>) override;
+ bool iterateDirectSubpaths(const DomItem &self, DirectVisitor) const override;
QSet<QString> const keys(const DomItem &self) const override;
- DomItem key(const DomItem &self, QString name) const override;
+ DomItem key(const DomItem &self, const QString &name) const override;
+
+ template<typename T>
+ static Map fromMultiMapRef(const Path &pathFromOwner, const QMultiMap<QString, T> &mmap);
+ template<typename T>
+ static Map fromMultiMap(const Path &pathFromOwner, const QMultiMap<QString, T> &mmap);
+ template<typename T>
+ static Map
+ fromMapRef(
+ const Path &pathFromOwner, const QMap<QString, T> &mmap,
+ const std::function<DomItem(const DomItem &, const PathEls::PathComponent &, const T &)> &elWrapper);
+
+ template<typename T>
+ static Map fromFileRegionMap(
+ const Path &pathFromOwner, const QMap<FileLocationRegion, T> &map);
+ template<typename T>
+ static Map fromFileRegionListMap(
+ const Path &pathFromOwner, const QMap<FileLocationRegion, QList<T>> &map);
- template <typename T>
- static Map fromMultiMapRef(Path pathFromOwner, QMultiMap<QString,T> &mmap, std::function<DomItem(const DomItem &, Path, T&)> elWrapper);
- template <typename T>
- static Map fromMapRef(Path pathFromOwner, QMap<QString,T> &mmap, std::function<DomItem(const DomItem &, Path, T&)> elWrapper);
private:
+ template<typename MapT>
+ static QSet<QString> fileRegionKeysFromMap(const MapT &map);
LookupFunction m_lookup;
Keys m_keys;
QString m_targetType;
};
-class QMLDOM_EXPORT List: public DomElement {
+class QMLDOM_EXPORT List final : public DomElement
+{
public:
constexpr static DomType kindValue = DomType::List;
DomType kind() const override { return kindValue; }
- using LookupFunction = std::function<DomItem (const DomItem &, index_type)>;
- using Length = std::function<index_type (const DomItem &)>;
- using IteratorFunction = std::function<bool (const DomItem &, std::function<bool(index_type,DomItem &)>)>;
+ List *operator->() { return this; }
+ const List *operator->() const { return this; }
+ List &operator*() { return *this; }
+ const List &operator*() const { return *this; }
- List(Path pathFromOwner, LookupFunction lookup, Length length, IteratorFunction iterator, QString elType);
+ using LookupFunction = std::function<DomItem(const DomItem &, index_type)>;
+ using Length = std::function<index_type(const DomItem &)>;
+ using IteratorFunction =
+ std::function<bool(const DomItem &, function_ref<bool(index_type, function_ref<DomItem()>)>)>;
+
+ List(const Path &pathFromOwner, const LookupFunction &lookup, const Length &length,
+ const IteratorFunction &iterator, const QString &elType);
quintptr id() const override;
- bool iterateDirectSubpaths(DomItem &self, std::function<bool(Path, DomItem &)>) override;
- void dump(const DomItem &, Sink s, int indent) const override;
+ bool iterateDirectSubpaths(const DomItem &self, DirectVisitor) const override;
+ void
+ dump(const DomItem &, const Sink &s, int indent,
+ function_ref<bool(const DomItem &, const PathEls::PathComponent &, const DomItem &)>) const override;
index_type indexes(const DomItem &self) const override;
DomItem index(const DomItem &self, index_type index) const override;
template<typename T>
- static List fromQList(Path pathFromOwner, QList<T> list, std::function<DomItem(const DomItem &, Path, T&)> elWrapper, ListOptions options = ListOptions::Normal);
+ static List
+ fromQList(const Path &pathFromOwner, const QList<T> &list,
+ const std::function<DomItem(const DomItem &, const PathEls::PathComponent &, const T &)> &elWrapper,
+ ListOptions options = ListOptions::Normal);
template<typename T>
- static List fromQListRef(Path pathFromOwner, QList<T> &list, std::function<DomItem(const DomItem &, Path, T&)> elWrapper, ListOptions options = ListOptions::Normal);
+ static List
+ fromQListRef(const Path &pathFromOwner, const QList<T> &list,
+ const std::function<DomItem(const DomItem &, const PathEls::PathComponent &, const T &)> &elWrapper,
+ ListOptions options = ListOptions::Normal);
+ void writeOut(const DomItem &self, OutWriter &ow, bool compact) const;
+ void writeOut(const DomItem &self, OutWriter &ow) const override { writeOut(self, ow, true); }
+
private:
LookupFunction m_lookup;
Length m_length;
@@ -221,18 +391,103 @@ private:
QString m_elType;
};
-class QMLDOM_EXPORT ConstantData: public DomElement {
+class QMLDOM_EXPORT ListPBase : public DomElement
+{
+public:
+ constexpr static DomType kindValue = DomType::ListP;
+ DomType kind() const override { return kindValue; }
+
+ ListPBase(const Path &pathFromOwner, const QList<const void *> &pList, const QString &elType)
+ : DomElement(pathFromOwner), m_pList(pList), m_elType(elType)
+ {
+ }
+ bool iterateDirectSubpaths(const DomItem &self, DirectVisitor v) const override;
+ virtual void copyTo(ListPBase *) const { Q_ASSERT(false); };
+ virtual void moveTo(ListPBase *) const { Q_ASSERT(false); };
+ quintptr id() const override { return quintptr(0); }
+ index_type indexes(const DomItem &) const override { return index_type(m_pList.size()); }
+ void writeOut(const DomItem &self, OutWriter &ow, bool compact) const;
+ void writeOut(const DomItem &self, OutWriter &ow) const override { writeOut(self, ow, true); }
+
+protected:
+ QList<const void *> m_pList;
+ QString m_elType;
+};
+
+template<typename T>
+class ListPT final : public ListPBase
+{
+public:
+ constexpr static DomType kindValue = DomType::ListP;
+
+ ListPT(const Path &pathFromOwner, const QList<T *> &pList, const QString &elType = QString(),
+ ListOptions options = ListOptions::Normal)
+ : ListPBase(pathFromOwner, {},
+ (elType.isEmpty() ? QLatin1String(typeid(T).name()) : elType))
+ {
+ static_assert(sizeof(ListPBase) == sizeof(ListPT),
+ "ListPT does not have the same size as ListPBase");
+ static_assert(alignof(ListPBase) == alignof(ListPT),
+ "ListPT does not have the same size as ListPBase");
+ m_pList.reserve(pList.size());
+ if (options == ListOptions::Normal) {
+ for (const void *p : pList)
+ m_pList.append(p);
+ } else if (options == ListOptions::Reverse) {
+ for (qsizetype i = pList.size(); i-- != 0;)
+ // probably writing in reverse and reading sequentially would be better
+ m_pList.append(pList.at(i));
+ } else {
+ Q_ASSERT(false);
+ }
+ }
+ void copyTo(ListPBase *t) const override { new (t) ListPT(*this); }
+ void moveTo(ListPBase *t) const override { new (t) ListPT(std::move(*this)); }
+ bool iterateDirectSubpaths(const DomItem &self, DirectVisitor v) const override;
+
+ DomItem index(const DomItem &self, index_type index) const override;
+};
+
+class QMLDOM_EXPORT ListP
+{
+public:
+ constexpr static DomType kindValue = DomType::ListP;
+ template<typename T>
+ ListP(const Path &pathFromOwner, const QList<T *> &pList, const QString &elType = QString(),
+ ListOptions options = ListOptions::Normal)
+ : list(ListPT<T>(pathFromOwner, pList, elType, options))
+ {
+ }
+ ListP() = delete;
+
+ ListPBase *operator->() { return list.data(); }
+ const ListPBase *operator->() const { return list.data(); }
+ ListPBase &operator*() { return *list.data(); }
+ const ListPBase &operator*() const { return *list.data(); }
+
+private:
+ SubclassStorage<ListPBase> list;
+};
+
+class QMLDOM_EXPORT ConstantData final : public DomElement
+{
public:
constexpr static DomType kindValue = DomType::ConstantData;
- DomType kind() const override { return kindValue; }
+ DomType kind() const override { return kindValue; }
enum class Options {
MapIsMap,
FirstMapIsFields
};
- ConstantData(Path pathFromOwner, QCborValue value, Options options = Options::MapIsMap, const SourceLocation & loc = SourceLocation());
- bool iterateDirectSubpaths(DomItem &self, std::function<bool(Path, DomItem &)>) override;
+ ConstantData *operator->() { return this; }
+ const ConstantData *operator->() const { return this; }
+ ConstantData &operator*() { return *this; }
+ const ConstantData &operator*() const { return *this; }
+
+ ConstantData(const Path &pathFromOwner, const QCborValue &value,
+ Options options = Options::MapIsMap);
+ bool iterateDirectSubpaths(const DomItem &self, DirectVisitor) const override;
quintptr id() const override;
DomKind domKind() const override;
QCborValue value() const override { return m_value; }
@@ -242,157 +497,595 @@ private:
Options m_options;
};
-class QMLDOM_EXPORT SimpleObjectWrap: public DomElement {
+class QMLDOM_EXPORT SimpleObjectWrapBase : public DomElement
+{
public:
constexpr static DomType kindValue = DomType::SimpleObjectWrap;
- DomType kind() const override { return kindValue; }
+ DomType kind() const final override { return m_kind; }
- template <typename T>
- static SimpleObjectWrap fromDataObject(
- Path pathFromOwner, T const & val,
- std::function<QCborValue(T const &)> toData,
- const SourceLocation & loc = SourceLocation(),
- DomType kind = kindValue,
- DomKind domKind = DomKind::Object,
- QString typeName = QString());
+ quintptr id() const final override { return m_id; }
+ DomKind domKind() const final override { return m_domKind; }
template <typename T>
- static SimpleObjectWrap fromObjectRef(
- Path pathFromOwner, T &value,
- std::function<bool(DomItem &, T &val, std::function<bool(Path, DomItem &)>)> directSubpathsIterate,
- const SourceLocation & loc = SourceLocation(),
- DomType kind = kindValue,
- QString typeName = QString(),
- DomKind domKind = DomKind::Object);
-
- bool iterateDirectSubpaths(DomItem &self, std::function<bool(Path, DomItem &)>) override;
- quintptr id() const override;
- QString typeName() const override { return m_typeName; }
- DomType internalKind() const { return m_kind; }
- DomKind domKind() const override { return m_domKind; }
- template <typename T>
T const *as() const
{
- return m_value.value<T*>();
+ if (m_options & SimpleWrapOption::ValueType) {
+ if (m_value.metaType() == QMetaType::fromType<T>())
+ return static_cast<const T *>(m_value.constData());
+ return nullptr;
+ } else {
+ return m_value.value<const T *>();
+ }
}
- template <typename T>
- T *mutableAs()
+
+ SimpleObjectWrapBase() = delete;
+ virtual void copyTo(SimpleObjectWrapBase *) const { Q_ASSERT(false); }
+ virtual void moveTo(SimpleObjectWrapBase *) const { Q_ASSERT(false); }
+ bool iterateDirectSubpaths(const DomItem &, DirectVisitor) const override
+ {
+ Q_ASSERT(false);
+ return true;
+ }
+
+protected:
+ friend class TestDomItem;
+ SimpleObjectWrapBase(const Path &pathFromOwner, const QVariant &value, quintptr idValue,
+ DomType kind = kindValue,
+ SimpleWrapOptions options = SimpleWrapOption::None)
+ : DomElement(pathFromOwner),
+ m_kind(kind),
+ m_domKind(kind2domKind(kind)),
+ m_value(value),
+ m_id(idValue),
+ m_options(options)
{
- return m_value.value<T*>();
}
-private:
- SimpleObjectWrap(
- Path pathFromOwner, QVariant value,
- std::function<bool(DomItem &, QVariant, std::function<bool(Path, DomItem &)>)> directSubpathsIterate,
- DomType kind = kindValue,
- DomKind domKind = DomKind::Object,
- QString typeName = QString(),
- const SourceLocation & loc = SourceLocation());
DomType m_kind;
DomKind m_domKind;
- QString m_typeName;
QVariant m_value;
- std::function<bool(DomItem &, QVariant, std::function<bool(Path, DomItem &)>)> m_directSubpathsIterate;
+ quintptr m_id;
+ SimpleWrapOptions m_options;
};
-class QMLDOM_EXPORT Reference: public DomElement {
+template<typename T>
+class SimpleObjectWrapT final : public SimpleObjectWrapBase
+{
+public:
+ constexpr static DomType kindValue = DomType::SimpleObjectWrap;
+
+ bool iterateDirectSubpaths(const DomItem &self, DirectVisitor visitor) const override
+ {
+ return asT()->iterateDirectSubpaths(self, visitor);
+ }
+
+ void writeOut(const DomItem &self, OutWriter &lw) const override;
+
+ const T *asT() const
+ {
+ if constexpr (domTypeIsValueWrap(T::kindValue)) {
+ if (m_value.metaType() == QMetaType::fromType<T>())
+ return static_cast<const T *>(m_value.constData());
+ return nullptr;
+ } else if constexpr (domTypeIsObjWrap(T::kindValue)) {
+ return m_value.value<const T *>();
+ } else {
+ // need dependent static assert to not unconditially trigger
+ static_assert(!std::is_same_v<T, T>, "wrapping of unexpected type");
+ return nullptr; // necessary to avoid warnings on INTEGRITY
+ }
+ }
+
+ void copyTo(SimpleObjectWrapBase *target) const override
+ {
+ static_assert(sizeof(SimpleObjectWrapBase) == sizeof(SimpleObjectWrapT),
+ "Size mismatch in SimpleObjectWrapT");
+ static_assert(alignof(SimpleObjectWrapBase) == alignof(SimpleObjectWrapT),
+ "Size mismatch in SimpleObjectWrapT");
+ new (target) SimpleObjectWrapT(*this);
+ }
+
+ void moveTo(SimpleObjectWrapBase *target) const override
+ {
+ static_assert(sizeof(SimpleObjectWrapBase) == sizeof(SimpleObjectWrapT),
+ "Size mismatch in SimpleObjectWrapT");
+ static_assert(alignof(SimpleObjectWrapBase) == alignof(SimpleObjectWrapT),
+ "Size mismatch in SimpleObjectWrapT");
+ new (target) SimpleObjectWrapT(std::move(*this));
+ }
+
+ SimpleObjectWrapT(const Path &pathFromOwner, const QVariant &v,
+ quintptr idValue, SimpleWrapOptions o)
+ : SimpleObjectWrapBase(pathFromOwner, v, idValue, T::kindValue, o)
+ {
+ Q_ASSERT(domTypeIsValueWrap(T::kindValue) == bool(o & SimpleWrapOption::ValueType));
+ }
+};
+
+class QMLDOM_EXPORT SimpleObjectWrap
+{
+public:
+ constexpr static DomType kindValue = DomType::SimpleObjectWrap;
+
+ SimpleObjectWrapBase *operator->() { return wrap.data(); }
+ const SimpleObjectWrapBase *operator->() const { return wrap.data(); }
+ SimpleObjectWrapBase &operator*() { return *wrap.data(); }
+ const SimpleObjectWrapBase &operator*() const { return *wrap.data(); }
+
+ template<typename T>
+ static SimpleObjectWrap fromObjectRef(const Path &pathFromOwner, T &value)
+ {
+ return SimpleObjectWrap(pathFromOwner, value);
+ }
+ SimpleObjectWrap() = delete;
+
+private:
+ template<typename T>
+ SimpleObjectWrap(const Path &pathFromOwner, T &value)
+ {
+ using BaseT = std::decay_t<T>;
+ if constexpr (domTypeIsObjWrap(BaseT::kindValue)) {
+ new (wrap.data()) SimpleObjectWrapT<BaseT>(pathFromOwner, QVariant::fromValue(&value),
+ quintptr(&value), SimpleWrapOption::None);
+ } else if constexpr (domTypeIsValueWrap(BaseT::kindValue)) {
+ new (wrap.data()) SimpleObjectWrapT<BaseT>(pathFromOwner, QVariant::fromValue(value),
+ quintptr(0), SimpleWrapOption::ValueType);
+ } else {
+ qCWarning(domLog) << "Unexpected object to wrap in SimpleObjectWrap: "
+ << domTypeToString(BaseT::kindValue);
+ Q_ASSERT_X(false, "SimpleObjectWrap",
+ "simple wrap of unexpected object"); // allow? (mocks for testing,...)
+ new (wrap.data())
+ SimpleObjectWrapT<BaseT>(pathFromOwner, nullptr, 0, SimpleWrapOption::None);
+ }
+ }
+ SubclassStorage<SimpleObjectWrapBase> wrap;
+};
+
+class QMLDOM_EXPORT Reference final : public DomElement
+{
+ Q_GADGET
public:
constexpr static DomType kindValue = DomType::Reference;
DomType kind() const override { return kindValue; }
- Reference(Path referredObject = Path(), Path pathFromOwner = Path(), const SourceLocation & loc = SourceLocation());
+ Reference *operator->() { return this; }
+ const Reference *operator->() const { return this; }
+ Reference &operator*() { return *this; }
+ const Reference &operator*() const { return *this; }
+
+ bool shouldCache() const;
+ Reference(const Path &referredObject = Path(), const Path &pathFromOwner = Path(),
+ const SourceLocation &loc = SourceLocation());
quintptr id() const override;
- bool iterateDirectSubpaths(DomItem &self, std::function<bool(Path, DomItem &)>) override;
+ bool iterateDirectSubpaths(const DomItem &self, DirectVisitor) const override;
DomItem field(const DomItem &self, QStringView name) const override;
- QList<QString> const fields(const DomItem &self) const override;
- index_type indexes(const DomItem &) const override {
- return 0;
- }
+ QList<QString> fields(const DomItem &self) const override;
+ index_type indexes(const DomItem &) const override { return 0; }
DomItem index(const DomItem &, index_type) const override;
- QSet<QString> const keys(const DomItem &) const override {
- return {};
- }
- DomItem key(const DomItem &, QString) const override;
+ QSet<QString> const keys(const DomItem &) const override { return {}; }
+ DomItem key(const DomItem &, const QString &) const override;
- DomItem get(const DomItem &self) const;
+ DomItem get(const DomItem &self, const ErrorHandler &h = nullptr,
+ QList<Path> *visitedRefs = nullptr) const;
+ QList<DomItem> getAll(const DomItem &self, const ErrorHandler &h = nullptr,
+ QList<Path> *visitedRefs = nullptr) const;
Path referredObjectPath;
};
+template<typename Info>
+class AttachedInfoT;
+class FileLocations;
+
+/*!
+ \internal
+ \brief A common base class for all the script elements.
+
+ This marker class allows to use all the script elements as a ScriptElement*, using virtual
+ dispatch. For now, it does not add any extra functionality, compared to a DomElement, but allows
+ to forbid DomElement* at the places where only script elements are required.
+ */
+// TODO: do we need another marker struct like this one to differentiate expressions from
+// statements? This would allow to avoid mismatchs between script expressions and script statements,
+// using type-safety.
+struct ScriptElement : public DomElement
+{
+ template<typename T>
+ using PointerType = std::shared_ptr<T>;
+
+ using DomElement::DomElement;
+ virtual void createFileLocations(
+ const std::shared_ptr<AttachedInfoT<FileLocations>> &fileLocationOfOwner) = 0;
+
+ QQmlJSScope::ConstPtr semanticScope();
+ void setSemanticScope(const QQmlJSScope::ConstPtr &scope);
+
+private:
+ QQmlJSScope::ConstPtr m_scope;
+};
+
+/*!
+ \internal
+ \brief Use this to contain any script element.
+ */
+class ScriptElementVariant
+{
+private:
+ template<typename... T>
+ using VariantOfPointer = std::variant<ScriptElement::PointerType<T>...>;
+
+ template<typename T, typename Variant>
+ struct TypeIsInVariant;
+
+ template<typename T, typename... Ts>
+ struct TypeIsInVariant<T, std::variant<Ts...>> : public std::disjunction<std::is_same<T, Ts>...>
+ {
+ };
+
+public:
+ using ScriptElementT =
+ VariantOfPointer<ScriptElements::BlockStatement, ScriptElements::IdentifierExpression,
+ ScriptElements::ForStatement, ScriptElements::BinaryExpression,
+ ScriptElements::VariableDeclarationEntry, ScriptElements::Literal,
+ ScriptElements::IfStatement, ScriptElements::GenericScriptElement,
+ ScriptElements::VariableDeclaration, ScriptElements::ReturnStatement>;
+
+ template<typename T>
+ static ScriptElementVariant fromElement(const T &element)
+ {
+ static_assert(TypeIsInVariant<T, ScriptElementT>::value,
+ "Cannot construct ScriptElementVariant from T, as it is missing from the "
+ "ScriptElementT.");
+ ScriptElementVariant p;
+ p.m_data = element;
+ return p;
+ }
+
+ ScriptElement::PointerType<ScriptElement> base() const;
+
+ operator bool() const { return m_data.has_value(); }
+
+ template<typename F>
+ void visitConst(F &&visitor) const
+ {
+ if (m_data)
+ std::visit(std::forward<F>(visitor), *m_data);
+ }
+
+ template<typename F>
+ void visit(F &&visitor)
+ {
+ if (m_data)
+ std::visit(std::forward<F>(visitor), *m_data);
+ }
+ std::optional<ScriptElementT> data() { return m_data; }
+ void setData(const ScriptElementT &data) { m_data = data; }
+
+private:
+ std::optional<ScriptElementT> m_data;
+};
+
+/*!
+ \internal
+
+ To avoid cluttering the already unwieldy \l ElementT type below with all the types that the
+ different script elements can have, wrap them in an extra class. It will behave like an internal
+ Dom structure (e.g. like a List or a Map) and contain a pointer the the script element.
+ */
+class ScriptElementDomWrapper
+{
+public:
+ ScriptElementDomWrapper(const ScriptElementVariant &element) : m_element(element) { }
+
+ static constexpr DomType kindValue = DomType::ScriptElementWrap;
+
+ DomBase *operator->() { return m_element.base().get(); }
+ const DomBase *operator->() const { return m_element.base().get(); }
+ DomBase &operator*() { return *m_element.base(); }
+ const DomBase &operator*() const { return *m_element.base(); }
+
+ ScriptElementVariant element() const { return m_element; }
+
+private:
+ ScriptElementVariant m_element;
+};
+
+// TODO: create more "groups" to simplify this variant? Maybe into Internal, ScriptExpression, ???
+using ElementT =
+ std::variant<
+ ConstantData,
+ Empty,
+ List,
+ ListP,
+ Map,
+ Reference,
+ ScriptElementDomWrapper,
+ SimpleObjectWrap,
+ const AstComments *,
+ const AttachedInfo *,
+ const DomEnvironment *,
+ const DomUniverse *,
+ const EnumDecl *,
+ const ExternalItemInfoBase *,
+ const ExternalItemPairBase *,
+ const GlobalComponent *,
+ const GlobalScope *,
+ const JsFile *,
+ const JsResource *,
+ const LoadInfo *,
+ const MockObject *,
+ const MockOwner *,
+ const ModuleIndex *,
+ const ModuleScope *,
+ const QmlComponent *,
+ const QmlDirectory *,
+ const QmlFile *,
+ const QmlObject *,
+ const QmldirFile *,
+ const QmltypesComponent *,
+ const QmltypesFile *,
+ const ScriptExpression *
+ >;
+
+using TopT = std::variant<
+ std::monostate,
+ std::shared_ptr<DomEnvironment>,
+ std::shared_ptr<DomUniverse>>;
+
+using OwnerT = std::variant<
+ std::monostate,
+ std::shared_ptr<ModuleIndex>,
+ std::shared_ptr<MockOwner>,
+ std::shared_ptr<ExternalItemInfoBase>,
+ std::shared_ptr<ExternalItemPairBase>,
+ std::shared_ptr<QmlDirectory>,
+ std::shared_ptr<QmldirFile>,
+ std::shared_ptr<JsFile>,
+ std::shared_ptr<QmlFile>,
+ std::shared_ptr<QmltypesFile>,
+ std::shared_ptr<GlobalScope>,
+ std::shared_ptr<ScriptExpression>,
+ std::shared_ptr<AstComments>,
+ std::shared_ptr<LoadInfo>,
+ std::shared_ptr<AttachedInfo>,
+ std::shared_ptr<DomEnvironment>,
+ std::shared_ptr<DomUniverse>>;
+
+inline bool emptyChildrenVisitor(Path, const DomItem &, bool)
+{
+ return true;
+}
+
class MutableDomItem;
+class FileToLoad
+{
+public:
+ struct InMemoryContents
+ {
+ QString data;
+ QDateTime date = QDateTime::currentDateTimeUtc();
+ };
+
+ FileToLoad(const std::weak_ptr<DomEnvironment> &environment, const QString &canonicalPath,
+ const QString &logicalPath, const std::optional<InMemoryContents> &content);
+ FileToLoad() = default;
+
+ static FileToLoad fromMemory(const std::weak_ptr<DomEnvironment> &environment,
+ const QString &path, const QString &data);
+ static FileToLoad fromFileSystem(const std::weak_ptr<DomEnvironment> &environment,
+ const QString &canonicalPath);
+
+ std::weak_ptr<DomEnvironment> environment() const { return m_environment; }
+ QString canonicalPath() const { return m_canonicalPath; }
+ QString logicalPath() const { return m_logicalPath; }
+ std::optional<InMemoryContents> content() const { return m_content; }
+
+private:
+ std::weak_ptr<DomEnvironment> m_environment;
+ QString m_canonicalPath;
+ QString m_logicalPath;
+ std::optional<InMemoryContents> m_content;
+};
+
class QMLDOM_EXPORT DomItem {
Q_DECLARE_TR_FUNCTIONS(DomItem);
public:
- using Callback = function<void(Path, const DomItem &, const DomItem &)>;
+ using Callback = function<void(const Path &, const DomItem &, const DomItem &)>;
using InternalKind = DomType;
- using Visitor = std::function<bool(Path, const DomItem &)>;
- using ChildrenVisitor = std::function<bool(Path, const DomItem &, bool)>;
+ using Visitor = function_ref<bool(const Path &, const DomItem &)>;
+ using ChildrenVisitor = function_ref<bool(const Path &, const DomItem &, bool)>;
static ErrorGroup domErrorGroup;
static ErrorGroups myErrors();
static ErrorGroups myResolveErrors();
+ static DomItem empty;
- operator bool() const { return base()->kind() != DomType::Empty; }
+ enum class CopyOption { EnvConnected, EnvDisconnected };
+
+ template<typename F>
+ auto visitEl(F f) const
+ {
+ return std::visit(f, this->m_element);
+ }
+
+ explicit operator bool() const { return m_kind != DomType::Empty; }
InternalKind internalKind() const {
- InternalKind res = base()->kind();
- if (res == InternalKind::SimpleObjectWrap)
- return static_cast<SimpleObjectWrap const *>(base())->internalKind();
- return res;
+ return m_kind;
}
- DomKind domKind() const {
- return base()->domKind();
+ QString internalKindStr() const { return domTypeToString(internalKind()); }
+ DomKind domKind() const
+ {
+ if (m_kind == DomType::ConstantData)
+ return std::get<ConstantData>(m_element).domKind();
+ else
+ return kind2domKind(m_kind);
}
Path canonicalPath() const;
+
+ DomItem filterUp(function_ref<bool(DomType k, const DomItem &)> filter, FilterUpOptions options) const;
DomItem containingObject() const;
DomItem container() const;
- DomItem component() const;
DomItem owner() const;
DomItem top() const;
DomItem environment() const;
DomItem universe() const;
+ DomItem containingFile() const;
+ DomItem containingScriptExpression() const;
+ DomItem goToFile(const QString &filePath) const;
+ DomItem goUp(int) const;
+ DomItem directParent() const;
+
+ DomItem qmlObject(GoTo option = GoTo::Strict,
+ FilterUpOptions options = FilterUpOptions::ReturnOuter) const;
+ DomItem fileObject(GoTo option = GoTo::Strict) const;
+ DomItem rootQmlObject(GoTo option = GoTo::Strict) const;
+ DomItem globalScope() const;
+ DomItem component(GoTo option = GoTo::Strict) const;
+ DomItem scope(FilterUpOptions options = FilterUpOptions::ReturnOuter) const;
+ QQmlJSScope::ConstPtr nearestSemanticScope() const;
+ QQmlJSScope::ConstPtr semanticScope() const;
// convenience getters
- QString name() const;
- DomItem qmlChildren() const;
- DomItem annotations() const;
+ DomItem get(const ErrorHandler &h = nullptr, QList<Path> *visitedRefs = nullptr) const;
+ QList<DomItem> getAll(const ErrorHandler &h = nullptr, QList<Path> *visitedRefs = nullptr) const;
+ bool isOwningItem() const { return domTypeIsOwningItem(internalKind()); }
+ bool isExternalItem() const { return domTypeIsExternalItem(internalKind()); }
+ bool isTopItem() const { return domTypeIsTopItem(internalKind()); }
+ bool isContainer() const { return domTypeIsContainer(internalKind()); }
+ bool isScope() const { return domTypeIsScope(internalKind()); }
+ bool isCanonicalChild(const DomItem &child) const;
+ bool hasAnnotations() const;
+ QString name() const { return field(Fields::name).value().toString(); }
+ DomItem pragmas() const { return field(Fields::pragmas); }
+ DomItem ids() const { return field(Fields::ids); }
+ QString idStr() const { return field(Fields::idStr).value().toString(); }
+ DomItem propertyInfos() const { return field(Fields::propertyInfos); }
+ PropertyInfo propertyInfoWithName(const QString &name) const;
+ QSet<QString> propertyInfoNames() const;
+ DomItem propertyDefs() const { return field(Fields::propertyDefs); }
+ DomItem bindings() const { return field(Fields::bindings); }
+ DomItem methods() const { return field(Fields::methods); }
+ DomItem enumerations() const { return field(Fields::enumerations); }
+ DomItem children() const { return field(Fields::children); }
+ DomItem child(index_type i) const { return field(Fields::children).index(i); }
+ DomItem annotations() const
+ {
+ if (hasAnnotations())
+ return field(Fields::annotations);
+ else
+ return DomItem();
+ }
- bool resolve(Path path, Visitor visitor, ErrorHandler errorHandler, ResolveOptions options = ResolveOption::None, Path fullPath = Path(), QList<Path> *visitedRefs = nullptr) const;
+ bool resolve(const Path &path, Visitor visitor, const ErrorHandler &errorHandler,
+ ResolveOptions options = ResolveOption::None, const Path &fullPath = Path(),
+ QList<Path> *visitedRefs = nullptr) const;
- DomItem operator[](Path path) const;
+ DomItem operator[](const Path &path) const;
DomItem operator[](QStringView component) const;
DomItem operator[](const QString &component) const;
- DomItem operator[](const char16_t *component) const { return (*this)[QStringView(component)]; } // to avoid clash with stupid builtin ptrdiff_t[DomItem&], coming from C
+ DomItem operator[](const char16_t *component) const
+ {
+ return (*this)[QStringView(component)];
+ } // to avoid clash with stupid builtin ptrdiff_t[DomItem&], coming from C
DomItem operator[](index_type i) const { return index(i); }
DomItem operator[](int i) const { return index(i); }
+ index_type size() const { return indexes() + keys().size(); }
+ index_type length() const { return size(); }
- DomItem path(Path p, ErrorHandler h = &defaultErrorHandler) const;
- DomItem path(QString p, ErrorHandler h = &defaultErrorHandler) const;
- DomItem path(QStringView p, ErrorHandler h = &defaultErrorHandler) const;
+ DomItem path(const Path &p, const ErrorHandler &h = &defaultErrorHandler) const;
+ DomItem path(const QString &p, const ErrorHandler &h = &defaultErrorHandler) const;
+ DomItem path(QStringView p, const ErrorHandler &h = &defaultErrorHandler) const;
- QList<QString> const fields() const;
+ QList<QString> fields() const;
DomItem field(QStringView name) const;
index_type indexes() const;
DomItem index(index_type) const;
-
- QSet<QString> const keys() const;
- DomItem key(QString name) const;
-
- bool visitChildren(Path basePath, ChildrenVisitor visitor, VisitOptions options = VisitOption::VisitAdopted, ChildrenVisitor openingVisitor = ChildrenVisitor(), ChildrenVisitor closingVisitor = ChildrenVisitor()) const;
-
- quintptr id() const { return base()->id(); }
- Path pathFromOwner() const { return base()->pathFromOwner(*this); }
- QString canonicalFilePath() const { return base()->canonicalFilePath(*this); }
- SourceLocation location() const { return base()->location(*this); }
-
+ bool visitIndexes(function_ref<bool(const DomItem &)> visitor) const;
+
+ QSet<QString> keys() const;
+ QStringList sortedKeys() const;
+ DomItem key(const QString &name) const;
+ DomItem key(QStringView name) const { return key(name.toString()); }
+ bool visitKeys(function_ref<bool(const QString &, const DomItem &)> visitor) const;
+
+ QList<DomItem> values() const;
+ void writeOutPre(OutWriter &lw) const;
+ void writeOut(OutWriter &lw) const;
+ void writeOutPost(OutWriter &lw) const;
+ bool writeOutForFile(OutWriter &ow, WriteOutChecks extraChecks) const;
+ bool writeOut(const QString &path, int nBackups = 2,
+ const LineWriterOptions &opt = LineWriterOptions(), FileWriter *fw = nullptr,
+ WriteOutChecks extraChecks = WriteOutCheck::Default) const;
+
+ bool visitTree(const Path &basePath, ChildrenVisitor visitor,
+ VisitOptions options = VisitOption::Default,
+ ChildrenVisitor openingVisitor = emptyChildrenVisitor,
+ ChildrenVisitor closingVisitor = emptyChildrenVisitor,
+ const FieldFilter &filter = FieldFilter::noFilter()) const;
+ bool visitPrototypeChain(function_ref<bool(const DomItem &)> visitor,
+ VisitPrototypesOptions options = VisitPrototypesOption::Normal,
+ const ErrorHandler &h = nullptr, QSet<quintptr> *visited = nullptr,
+ QList<Path> *visitedRefs = nullptr) const;
+ bool visitDirectAccessibleScopes(function_ref<bool(const DomItem &)> visitor,
+ VisitPrototypesOptions options = VisitPrototypesOption::Normal,
+ const ErrorHandler &h = nullptr, QSet<quintptr> *visited = nullptr,
+ QList<Path> *visitedRefs = nullptr) const;
+ bool
+ visitStaticTypePrototypeChains(function_ref<bool(const DomItem &)> visitor,
+ VisitPrototypesOptions options = VisitPrototypesOption::Normal,
+ const ErrorHandler &h = nullptr, QSet<quintptr> *visited = nullptr,
+ QList<Path> *visitedRefs = nullptr) const;
+
+ bool visitUp(function_ref<bool(const DomItem &)> visitor) const;
+ bool visitScopeChain(
+ function_ref<bool(const DomItem &)> visitor, LookupOptions = LookupOption::Normal,
+ const ErrorHandler &h = nullptr, QSet<quintptr> *visited = nullptr,
+ QList<Path> *visitedRefs = nullptr) const;
+ bool visitLocalSymbolsNamed(
+ const QString &name, function_ref<bool(const DomItem &)> visitor) const;
+ bool visitLookup1(
+ const QString &symbolName, function_ref<bool(const DomItem &)> visitor,
+ LookupOptions = LookupOption::Normal, const ErrorHandler &h = nullptr,
+ QSet<quintptr> *visited = nullptr, QList<Path> *visitedRefs = nullptr) const;
+ bool visitLookup(
+ const QString &symbolName, function_ref<bool(const DomItem &)> visitor,
+ LookupType type = LookupType::Symbol, LookupOptions = LookupOption::Normal,
+ const ErrorHandler &errorHandler = nullptr, QSet<quintptr> *visited = nullptr,
+ QList<Path> *visitedRefs = nullptr) const;
+ bool visitSubSymbolsNamed(
+ const QString &name, function_ref<bool(const DomItem &)> visitor) const;
+ DomItem proceedToScope(
+ const ErrorHandler &h = nullptr, QList<Path> *visitedRefs = nullptr) const;
+ QList<DomItem> lookup(
+ const QString &symbolName, LookupType type = LookupType::Symbol,
+ LookupOptions = LookupOption::Normal, const ErrorHandler &errorHandler = nullptr) const;
+ DomItem lookupFirst(
+ const QString &symbolName, LookupType type = LookupType::Symbol,
+ LookupOptions = LookupOption::Normal, const ErrorHandler &errorHandler = nullptr) const;
+
+ quintptr id() const;
+ Path pathFromOwner() const;
+ QString canonicalFilePath() const;
+ DomItem fileLocationsTree() const;
+ DomItem fileLocations() const;
+ MutableDomItem makeCopy(CopyOption option = CopyOption::EnvConnected) const;
+ bool commitToBase(const std::shared_ptr<DomEnvironment> &validPtr = nullptr) const;
+ DomItem refreshed() const { return top().path(canonicalPath()); }
QCborValue value() const;
- void dumpPtr(Sink) const;
- void dump(Sink, int indent = 0) const;
+ void dumpPtr(const Sink &sink) const;
+ void dump(const Sink &, int indent = 0,
+ function_ref<bool(const DomItem &, const PathEls::PathComponent &, const DomItem &)> filter =
+ noFilter) const;
+ FileWriter::Status
+ dump(const QString &path,
+ function_ref<bool(const DomItem &, const PathEls::PathComponent &, const DomItem &)> filter = noFilter,
+ int nBackups = 2, int indent = 0, FileWriter *fw = nullptr) const;
QString toString() const;
// OwnigItem elements
@@ -402,54 +1095,126 @@ public:
QDateTime frozenAt() const;
QDateTime lastDataUpdateAt() const;
- void addError(ErrorMessage msg) const;
+ void addError(ErrorMessage &&msg) const;
ErrorHandler errorHandler() const;
- void clearErrors(ErrorGroups groups = ErrorGroups({}), bool iterate = true) const;
+ void clearErrors(const ErrorGroups &groups = ErrorGroups({}), bool iterate = true) const;
// return false if a quick exit was requested
- bool iterateErrors(std::function<bool(DomItem source, ErrorMessage msg)> visitor, bool iterate,
- Path inPath = Path())const;
-
- bool iterateSubOwners(std::function<bool(DomItem owner)> visitor) const;
-
- Subpath subDataField(QStringView fieldName, QCborValue value, ConstantData::Options options = ConstantData::Options::MapIsMap, const SourceLocation &loc = SourceLocation()) const;
- Subpath subDataField(QString fieldName, QCborValue value, ConstantData::Options options = ConstantData::Options::MapIsMap, const SourceLocation &loc = SourceLocation()) const;
- Subpath subDataIndex(index_type i, QCborValue value, ConstantData::Options options = ConstantData::Options::MapIsMap, const SourceLocation &loc = SourceLocation()) const;
- Subpath subDataKey(QStringView keyName, QCborValue value, ConstantData::Options options = ConstantData::Options::MapIsMap, const SourceLocation &loc = SourceLocation()) const;
- Subpath subDataKey(QString keyName, QCborValue value, ConstantData::Options options = ConstantData::Options::MapIsMap, const SourceLocation &loc = SourceLocation()) const;
- Subpath subDataPath(Path path, QCborValue value, ConstantData::Options options = ConstantData::Options::MapIsMap, const SourceLocation &loc = SourceLocation()) const;
- Subpath subReferenceField(QStringView fieldName, Path referencedObject,
- const SourceLocation & loc = SourceLocation()) const;
- Subpath subReferenceField(QString fieldName, Path referencedObject, const SourceLocation & loc = SourceLocation()) const;
- Subpath subReferenceKey(QStringView keyName, Path referencedObject, const SourceLocation & loc = SourceLocation()) const;
- Subpath subReferenceKey(QString keyName, Path referencedObject, const SourceLocation & loc = SourceLocation()) const;
- Subpath subReferenceIndex(index_type i, Path referencedObject, const SourceLocation & loc = SourceLocation()) const;
- Subpath subReferencePath(Path subPath, Path referencedObject, const SourceLocation & loc = SourceLocation()) const;
-
- Subpath toSubField(QStringView fieldName) const;
- Subpath toSubField(QString fieldName) const;
- Subpath toSubKey(QStringView keyName) const;
- Subpath toSubKey(QString keyName) const;
- Subpath toSubIndex(index_type i) const;
- Subpath toSubPath(Path subPath) const;
- Subpath subList(const List &list) const;
- Subpath subMap(const Map &map) const;
- Subpath subObjectWrap(const SimpleObjectWrap &o) const;
- template <typename T>
- Subpath subWrapPath(Path p, T &obj, SourceLocation loc = SourceLocation()) const;
- template <typename T>
- Subpath subWrapField(QString p, T &obj, SourceLocation loc = SourceLocation()) const;
- template <typename T>
- Subpath subWrapField(QStringView p, T &obj, SourceLocation loc = SourceLocation()) const;
- template <typename T>
- Subpath subWrapKey(QString p, T &obj, SourceLocation loc = SourceLocation()) const;
- template <typename T>
- Subpath subWrapKey(QStringView p, T &obj, SourceLocation loc = SourceLocation()) const;
- template <typename T>
- Subpath subWrapIndex(index_type i, T &obj, SourceLocation loc = SourceLocation()) const;
+ bool iterateErrors(
+ function_ref<bool (const DomItem &, const ErrorMessage &)> visitor, bool iterate,
+ Path inPath = Path()) const;
- DomItem();
- DomItem(std::shared_ptr<DomEnvironment>);
- DomItem(std::shared_ptr<DomUniverse>);
+ bool iterateSubOwners(function_ref<bool(const DomItem &owner)> visitor) const;
+ bool iterateDirectSubpaths(DirectVisitor v) const;
+
+ template<typename T>
+ DomItem subDataItem(const PathEls::PathComponent &c, const T &value,
+ ConstantData::Options options = ConstantData::Options::MapIsMap) const;
+ template<typename T>
+ DomItem subDataItemField(QStringView f, const T &value,
+ ConstantData::Options options = ConstantData::Options::MapIsMap) const
+ {
+ return subDataItem(PathEls::Field(f), value, options);
+ }
+ template<typename T>
+ DomItem subValueItem(const PathEls::PathComponent &c, const T &value,
+ ConstantData::Options options = ConstantData::Options::MapIsMap) const;
+ template<typename T>
+ bool dvValue(DirectVisitor visitor, const PathEls::PathComponent &c, const T &value,
+ ConstantData::Options options = ConstantData::Options::MapIsMap) const;
+ template<typename T>
+ bool dvValueField(DirectVisitor visitor, QStringView f, const T &value,
+ ConstantData::Options options = ConstantData::Options::MapIsMap) const
+ {
+ return this->dvValue<T>(std::move(visitor), PathEls::Field(f), value, options);
+ }
+ template<typename F>
+ bool dvValueLazy(DirectVisitor visitor, const PathEls::PathComponent &c, F valueF,
+ ConstantData::Options options = ConstantData::Options::MapIsMap) const;
+ template<typename F>
+ bool dvValueLazyField(DirectVisitor visitor, QStringView f, F valueF,
+ ConstantData::Options options = ConstantData::Options::MapIsMap) const
+ {
+ return this->dvValueLazy(std::move(visitor), PathEls::Field(f), valueF, options);
+ }
+ DomItem subLocationItem(const PathEls::PathComponent &c, SourceLocation loc) const
+ {
+ return this->subDataItem(c, sourceLocationToQCborValue(loc));
+ }
+ // bool dvSubReference(DirectVisitor visitor, const PathEls::PathComponent &c, Path
+ // referencedObject);
+ DomItem subReferencesItem(const PathEls::PathComponent &c, const QList<Path> &paths) const;
+ DomItem subReferenceItem(const PathEls::PathComponent &c, const Path &referencedObject) const;
+ bool dvReference(DirectVisitor visitor, const PathEls::PathComponent &c, const Path &referencedObject) const
+ {
+ return dvItem(std::move(visitor), c, [c, this, referencedObject]() {
+ return this->subReferenceItem(c, referencedObject);
+ });
+ }
+ bool dvReferences(
+ DirectVisitor visitor, const PathEls::PathComponent &c, const QList<Path> &paths) const
+ {
+ return dvItem(std::move(visitor), c, [c, this, paths]() {
+ return this->subReferencesItem(c, paths);
+ });
+ }
+ bool dvReferenceField(DirectVisitor visitor, QStringView f, const Path &referencedObject) const
+ {
+ return dvReference(std::move(visitor), PathEls::Field(f), referencedObject);
+ }
+ bool dvReferencesField(DirectVisitor visitor, QStringView f, const QList<Path> &paths) const
+ {
+ return dvReferences(std::move(visitor), PathEls::Field(f), paths);
+ }
+ bool dvItem(DirectVisitor visitor, const PathEls::PathComponent &c, function_ref<DomItem()> it) const
+ {
+ return visitor(c, it);
+ }
+ bool dvItemField(DirectVisitor visitor, QStringView f, function_ref<DomItem()> it) const
+ {
+ return dvItem(std::move(visitor), PathEls::Field(f), it);
+ }
+ DomItem subListItem(const List &list) const;
+ DomItem subMapItem(const Map &map) const;
+ DomItem subObjectWrapItem(SimpleObjectWrap obj) const
+ {
+ return DomItem(m_top, m_owner, m_ownerPath, obj);
+ }
+
+ DomItem subScriptElementWrapperItem(const ScriptElementVariant &obj) const
+ {
+ Q_ASSERT(obj);
+ return DomItem(m_top, m_owner, m_ownerPath, ScriptElementDomWrapper(obj));
+ }
+
+ template<typename Owner>
+ DomItem subOwnerItem(const PathEls::PathComponent &c, Owner o) const
+ {
+ if constexpr (domTypeIsUnattachedOwningItem(Owner::element_type::kindValue))
+ return DomItem(m_top, o, canonicalPath().appendComponent(c), o.get());
+ else
+ return DomItem(m_top, o, Path(), o.get());
+ }
+ template<typename T>
+ DomItem wrap(const PathEls::PathComponent &c, const T &obj) const;
+ template<typename T>
+ DomItem wrapField(QStringView f, const T &obj) const
+ {
+ return wrap<T>(PathEls::Field(f), obj);
+ }
+ template<typename T>
+ bool dvWrap(DirectVisitor visitor, const PathEls::PathComponent &c, T &obj) const;
+ template<typename T>
+ bool dvWrapField(DirectVisitor visitor, QStringView f, T &obj) const
+ {
+ return dvWrap<T>(std::move(visitor), PathEls::Field(f), obj);
+ }
+
+ DomItem() = default;
+ DomItem(const std::shared_ptr<DomEnvironment> &);
+ DomItem(const std::shared_ptr<DomUniverse> &);
+
+ // TODO move to DomEnvironment?
+ static DomItem fromCode(const QString &code, DomType fileType = DomType::QmlFile);
// --- start of potentially dangerous stuff, make private? ---
@@ -457,64 +1222,100 @@ public:
std::shared_ptr<OwningItem> owningItemPtr() const;
// keep the DomItem around to ensure that it doesn't get deleted
- template <typename T, typename std::enable_if<std::is_base_of<DomBase, T>::value, bool>::type = true>
- T const*as() const {
- InternalKind k = base()->kind();
- if (k == T::kindValue)
- return static_cast<T const*>(base());
- if (k == InternalKind::SimpleObjectWrap)
- return static_cast<SimpleObjectWrap const *>(base())->as<T>();
+ template<typename T, typename std::enable_if<std::is_base_of_v<DomBase, T>, bool>::type = true>
+ T const *as() const
+ {
+ if (m_kind == T::kindValue) {
+ if constexpr (domTypeIsObjWrap(T::kindValue) || domTypeIsValueWrap(T::kindValue))
+ return std::get<SimpleObjectWrap>(m_element)->as<T>();
+ else
+ return static_cast<T const *>(base());
+ }
return nullptr;
}
- template <typename T, typename std::enable_if<!std::is_base_of<DomBase, T>::value, bool>::type = true>
- T const*as() const {
- InternalKind k = base()->kind();
- if (k == InternalKind::SimpleObjectWrap)
- return static_cast<SimpleObjectWrap const *>(base())->as<T>();
+ template<typename T, typename std::enable_if<!std::is_base_of_v<DomBase, T>, bool>::type = true>
+ T const *as() const
+ {
+ if (m_kind == T::kindValue) {
+ Q_ASSERT(domTypeIsObjWrap(m_kind) || domTypeIsValueWrap(m_kind));
+ return std::get<SimpleObjectWrap>(m_element)->as<T>();
+ }
return nullptr;
}
- template <typename T>
+ template<typename T>
std::shared_ptr<T> ownerAs() const;
- DomItem copy(std::shared_ptr<OwningItem> owner, DomBase *base) const;
- DomItem copy(std::shared_ptr<OwningItem> owner) const;
- DomItem copy(DomBase *base) const;
-private:
- DomBase const* base() const {
- if (m_base == nullptr)
- return reinterpret_cast<DomBase const*>(&inlineEl);
- return m_base;
+ template<typename Owner, typename T>
+ DomItem copy(const Owner &owner, const Path &ownerPath, const T &base) const
+ {
+ Q_ASSERT(!std::holds_alternative<std::monostate>(m_top));
+ static_assert(IsInlineDom<std::decay_t<T>>::value, "Expected an inline item or pointer");
+ return DomItem(m_top, owner, ownerPath, base);
}
- template <typename T, typename std::enable_if<std::is_base_of<DomBase, T>::value, bool>::type = true>
- T *mutableAs() {
- InternalKind k = base()->kind();
- if (k == T::kindValue)
- return static_cast<T*>(mutableBase());
- if (k == InternalKind::SimpleObjectWrap)
- return static_cast<SimpleObjectWrap *>(mutableBase())->mutableAs<T>();
- return nullptr;
+
+ template<typename Owner>
+ DomItem copy(const Owner &owner, const Path &ownerPath) const
+ {
+ Q_ASSERT(!std::holds_alternative<std::monostate>(m_top));
+ return DomItem(m_top, owner, ownerPath, owner.get());
}
- template <typename T, typename std::enable_if<!std::is_base_of<DomBase, T>::value, bool>::type = true>
- T *mutableAs() {
- InternalKind k = base()->kind();
- if (k == InternalKind::SimpleObjectWrap)
- return static_cast<SimpleObjectWrap *>(mutableBase())->mutableAs<T>();
- return nullptr;
+ template<typename T>
+ DomItem copy(const T &base) const
+ {
+ Q_ASSERT(!std::holds_alternative<std::monostate>(m_top));
+ using BaseT = std::decay_t<T>;
+ static_assert(!std::is_same_v<BaseT, ElementT>,
+ "variant not supported, pass in the stored types");
+ static_assert(IsInlineDom<BaseT>::value || std::is_same_v<BaseT, std::monostate>,
+ "expected either a pointer or an inline item");
+
+ if constexpr (IsSharedPointerToDomObject<BaseT>::value)
+ return DomItem(m_top, base, Path(), base.get());
+ else if constexpr (IsInlineDom<BaseT>::value)
+ return DomItem(m_top, m_owner, m_ownerPath, base);
+
+ Q_UNREACHABLE_RETURN(DomItem(m_top, m_owner, m_ownerPath, nullptr));
+ }
+
+private:
+ enum class WriteOutCheckResult { Success, Failed };
+ WriteOutCheckResult performWriteOutChecks(const DomItem &, const DomItem &, OutWriter &, WriteOutChecks) const;
+ const DomBase *base() const;
+
+ template<typename Env, typename Owner>
+ DomItem(Env, Owner, Path, std::nullptr_t) : DomItem()
+ {
+ }
+
+ template<typename Env, typename Owner, typename T,
+ typename = std::enable_if_t<IsInlineDom<std::decay_t<T>>::value>>
+ DomItem(Env env, Owner owner, const Path &ownerPath, const T &el)
+ : m_top(env), m_owner(owner), m_ownerPath(ownerPath), m_element(el)
+ {
+ using BaseT = std::decay_t<T>;
+ if constexpr (std::is_pointer_v<BaseT>) {
+ if (!el || el->kind() == DomType::Empty) { // avoid null ptr, and allow only a
+ // single kind of Empty
+ m_kind = DomType::Empty;
+ m_top = std::monostate();
+ m_owner = std::monostate();
+ m_ownerPath = Path();
+ m_element = Empty();
+ } else {
+ using DomT = std::remove_pointer_t<BaseT>;
+ m_element = el;
+ m_kind = DomT::kindValue;
+ }
+ } else {
+ static_assert(!std::is_same_v<BaseT, ElementT>,
+ "variant not supported, pass in the internal type");
+ m_kind = el->kind();
+ }
}
- DomBase * mutableBase() {
- if (m_base == nullptr)
- return reinterpret_cast<DomBase*>(&inlineEl);
- return m_base;
- }
- DomItem(std::shared_ptr<DomTop> env, std::shared_ptr<OwningItem> owner, DomBase *base);
- DomItem(std::shared_ptr<DomTop> env, std::shared_ptr<OwningItem> owner, Map map);
- DomItem(std::shared_ptr<DomTop> env, std::shared_ptr<OwningItem> owner, List list);
- DomItem(std::shared_ptr<DomTop> env, std::shared_ptr<OwningItem> owner, ConstantData data);
- DomItem(std::shared_ptr<DomTop> env, std::shared_ptr<OwningItem> owner, Reference reference);
- DomItem(std::shared_ptr<DomTop> env, std::shared_ptr<OwningItem> owner, SimpleObjectWrap wrapper);
+ friend class DomBase;
friend class DomElement;
friend class Map;
friend class List;
@@ -524,209 +1325,198 @@ private:
friend class ExternalItemInfoBase;
friend class ConstantData;
friend class MutableDomItem;
- friend bool operator ==(const DomItem &, const DomItem &);
- std::shared_ptr<DomTop> m_top;
- std::shared_ptr<OwningItem> m_owner;
- DomBase *m_base;
- union InlineEl {
- // Should add optimized move ops (should be able to do a bit copy of union)
- InlineEl(): empty() { }
- InlineEl(const InlineEl &d) {
- switch (d.kind()){
- case DomType::Empty:
- Q_ASSERT((quintptr)this == (quintptr)&empty && "non C++11 compliant compiler");
- new (&empty) Empty(d.empty);
- break;
- case DomType::Map:
- Q_ASSERT((quintptr)this == (quintptr)&map && "non C++11 compliant compiler");
- new (&map) Map(d.map);
- break;
- case DomType::List:
- Q_ASSERT((quintptr)this == (quintptr)&list && "non C++11 compliant compiler");
- new (&list) List(d.list);
- break;
- case DomType::ConstantData:
- Q_ASSERT((quintptr)this == (quintptr)&data && "non C++11 compliant compiler");
- new (&data) ConstantData(d.data);
- break;
- case DomType::SimpleObjectWrap:
- Q_ASSERT((quintptr)this == (quintptr)&simpleObjectWrap && "non C++11 compliant compiler");
- new (&simpleObjectWrap) SimpleObjectWrap(d.simpleObjectWrap);
- break;
- case DomType::Reference:
- Q_ASSERT((quintptr)this == (quintptr)&reference && "non C++11 compliant compiler");
- new (&reference) Reference(d.reference);
- break;
- default:
- Q_ASSERT(false && "unexpected kind in inline element");
- break;
- }
- }
- InlineEl(const Empty &o) {
- Q_ASSERT((quintptr)this == (quintptr)&empty && "non C++11 compliant compiler");
- new (&empty) Empty(o);
- }
- InlineEl(const Map &o) {
- Q_ASSERT((quintptr)this == (quintptr)&map && "non C++11 compliant compiler");
- new (&map) Map(o);
- }
- InlineEl(const List &o){
- Q_ASSERT((quintptr)this == (quintptr)&list && "non C++11 compliant compiler");
- new (&list) List(o);
- }
- InlineEl(const ConstantData &o) {
- Q_ASSERT((quintptr)this == (quintptr)&data && "non C++11 compliant compiler");
- new (&data) ConstantData(o);
- }
- InlineEl(const SimpleObjectWrap &o) {
- Q_ASSERT((quintptr)this == (quintptr)&simpleObjectWrap && "non C++11 compliant compiler");
- new (&simpleObjectWrap) SimpleObjectWrap(o);
- }
- InlineEl(const Reference &o) {
- Q_ASSERT((quintptr)this == (quintptr)&reference && "non C++11 compliant compiler");
- new (&reference) Reference(o);
- }
- InlineEl &operator=(const InlineEl &d) {
- Q_ASSERT(this != &d);
- this->~InlineEl(); // destruct & construct new...
- new (this)InlineEl(d);
- return *this;
- }
- DomType kind() const {
- return reinterpret_cast<const DomBase*>(this)->kind();
- }
- ~InlineEl() {
- reinterpret_cast<const DomBase*>(this)->~DomBase();
- }
- Empty empty;
- Map map;
- List list;
- ConstantData data;
- SimpleObjectWrap simpleObjectWrap;
- Reference reference;
- } inlineEl;
+ friend class ScriptExpression;
+ friend class AstComments;
+ friend class AttachedInfo;
+ friend class TestDomItem;
+ friend QMLDOM_EXPORT bool operator==(const DomItem &, const DomItem &);
+ DomType m_kind = DomType::Empty;
+ TopT m_top;
+ OwnerT m_owner;
+ Path m_ownerPath;
+ ElementT m_element = Empty();
};
-bool operator ==(const DomItem &o1, const DomItem &o2);
-inline bool operator !=(const DomItem &o1, const DomItem &o2) {
+QMLDOM_EXPORT bool operator==(const DomItem &o1, const DomItem &o2);
+
+inline bool operator!=(const DomItem &o1, const DomItem &o2)
+{
return !(o1 == o2);
}
-Q_DECLARE_OPERATORS_FOR_FLAGS(LoadOptions)
+template<typename T>
+static DomItem keyMultiMapHelper(const DomItem &self, const QString &key,
+ const QMultiMap<QString, T> &mmap)
+{
+ auto it = mmap.find(key);
+ auto end = mmap.cend();
+ if (it == end)
+ return DomItem();
+ else {
+ // special case single element (++it == end || it.key() != key)?
+ QList<const T *> values;
+ while (it != end && it.key() == key)
+ values.append(&(*it++));
+ ListP ll(self.pathFromOwner().appendComponent(PathEls::Key(key)), values, QString(),
+ ListOptions::Reverse);
+ return self.copy(ll);
+ }
+}
-class Subpath {
-public:
- Path path;
- DomItem item;
+template<typename T>
+Map Map::fromMultiMapRef(const Path &pathFromOwner, const QMultiMap<QString, T> &mmap)
+{
+ return Map(
+ pathFromOwner,
+ [&mmap](const DomItem &self, const QString &key) {
+ return keyMultiMapHelper(self, key, mmap);
+ },
+ [&mmap](const DomItem &) { return QSet<QString>(mmap.keyBegin(), mmap.keyEnd()); },
+ QLatin1String(typeid(T).name()));
+}
- bool visit(std::function <bool(Path, DomItem &)> visitor){
- return visitor(path, item);
- }
-};
+template<typename T>
+Map Map::fromMapRef(
+ const Path &pathFromOwner, const QMap<QString, T> &map,
+ const std::function<DomItem(const DomItem &, const PathEls::PathComponent &, const T &)> &elWrapper)
+{
+ return Map(
+ pathFromOwner,
+ [&map, elWrapper](const DomItem &self, const QString &key) {
+ const auto it = map.constFind(key);
+ if (it == map.constEnd())
+ return DomItem();
+ return elWrapper(self, PathEls::Key(key), it.value());
+ },
+ [&map](const DomItem &) { return QSet<QString>(map.keyBegin(), map.keyEnd()); },
+ QLatin1String(typeid(T).name()));
+}
+
+template<typename MapT>
+QSet<QString> Map::fileRegionKeysFromMap(const MapT &map)
+{
+ QSet<QString> keys;
+ std::transform(map.keyBegin(), map.keyEnd(), std::inserter(keys, keys.begin()), fileLocationRegionName);
+ return keys;
+}
template<typename T>
-Map Map::fromMultiMapRef(Path pathFromOwner, QMultiMap<QString,T> &mmap, std::function<DomItem(const DomItem &, Path, T&)> elWrapper)
+Map Map::fromFileRegionMap(const Path &pathFromOwner, const QMap<FileLocationRegion, T> &map)
{
- return Map(pathFromOwner, [&mmap, elWrapper](const DomItem &self, QString key) {
- auto it = mmap.find(key);
- auto end = mmap.cend();
- if (it == end)
- return DomItem();
- else {
- QList<T *> values;
- while (it != end && it.key() == key)
- values.append(&(*it++));
- return self.subList(List::fromQList<T*>(self.pathFromOwner().subKey(key), values, [elWrapper](const DomItem &l, Path p,T * &el) {
- return elWrapper(l,p,*el);
- }, ListOptions::Reverse)).item;
- }
- }, [&mmap](const DomItem&){
- return QSet<QString>(mmap.keyBegin(), mmap.keyEnd());
- }, QLatin1String(typeid(T).name()));
+ auto result = Map(
+ pathFromOwner,
+ [&map](const DomItem &mapItem, const QString &key) -> DomItem {
+ auto it = map.constFind(fileLocationRegionValue(key));
+ if (it == map.constEnd())
+ return {};
+
+ return mapItem.wrap(PathEls::Key(key), *it);
+ },
+ [&map](const DomItem &) { return fileRegionKeysFromMap(map); },
+ QString::fromLatin1(typeid(T).name()));
+ return result;
}
template<typename T>
-Map Map::fromMapRef(Path pathFromOwner, QMap<QString,T> &map,
- std::function<DomItem(const DomItem &, Path, T&)> elWrapper)
+Map Map::fromFileRegionListMap(const Path &pathFromOwner,
+ const QMap<FileLocationRegion, QList<T>> &map)
{
- return Map(pathFromOwner, [&map, elWrapper](const DomItem &self, QString key) {
- if (!map.contains(key))
- return DomItem();
- else {
- return elWrapper(self, Path::key(key), map[key]);
- }
- }, [&map](const DomItem&){
- return QSet<QString>(map.keyBegin(), map.keyEnd());
- }, QLatin1String(typeid(T).name()));
+ using namespace Qt::StringLiterals;
+ auto result = Map(
+ pathFromOwner,
+ [&map](const DomItem &mapItem, const QString &key) -> DomItem {
+ const QList<SourceLocation> locations = map.value(fileLocationRegionValue(key));
+ if (locations.empty())
+ return {};
+
+ auto list = List::fromQList<SourceLocation>(
+ mapItem.pathFromOwner(), locations,
+ [](const DomItem &self, const PathEls::PathComponent &path,
+ const SourceLocation &location) {
+ return self.subLocationItem(path, location);
+ });
+ return mapItem.subListItem(list);
+ },
+ [&map](const DomItem &) { return fileRegionKeysFromMap(map); },
+ u"QList<%1>"_s.arg(QString::fromLatin1(typeid(T).name())));
+ return result;
}
template<typename T>
-List List::fromQList(Path pathFromOwner, QList<T> list, std::function<DomItem(const DomItem &, Path, T&)> elWrapper, ListOptions options)
+List List::fromQList(
+ const Path &pathFromOwner, const QList<T> &list,
+ const std::function<DomItem(const DomItem &, const PathEls::PathComponent &, const T &)> &elWrapper,
+ ListOptions options)
{
- index_type len = list.length();
+ index_type len = list.size();
if (options == ListOptions::Reverse) {
return List(
- pathFromOwner,
- [list, elWrapper](const DomItem &self, index_type i) mutable {
- if (i < 0 || i >= list.length())
- return DomItem();
- return elWrapper(self, Path::index(i), list[list.length() -i - 1]);
- }, [len](const DomItem &) {
- return len;
- }, nullptr, QLatin1String(typeid(T).name()));
+ pathFromOwner,
+ [list, elWrapper](const DomItem &self, index_type i) mutable {
+ if (i < 0 || i >= list.size())
+ return DomItem();
+ return elWrapper(self, PathEls::Index(i), list[list.size() - i - 1]);
+ },
+ [len](const DomItem &) { return len; }, nullptr, QLatin1String(typeid(T).name()));
} else {
- return List(pathFromOwner,
- [list, elWrapper](const DomItem &self, index_type i) mutable {
- if (i < 0 || i >= list.length())
- return DomItem();
- return elWrapper(self, Path::index(i), list[i]);
- }, [len](const DomItem &) {
- return len;
- }, nullptr, QLatin1String(typeid(T).name()));
+ return List(
+ pathFromOwner,
+ [list, elWrapper](const DomItem &self, index_type i) mutable {
+ if (i < 0 || i >= list.size())
+ return DomItem();
+ return elWrapper(self, PathEls::Index(i), list[i]);
+ },
+ [len](const DomItem &) { return len; }, nullptr, QLatin1String(typeid(T).name()));
}
}
template<typename T>
-List List::fromQListRef(Path pathFromOwner, QList<T> &list, std::function<DomItem(const DomItem &, Path, T&)> elWrapper, ListOptions options)
+List List::fromQListRef(
+ const Path &pathFromOwner, const QList<T> &list,
+ const std::function<DomItem(const DomItem &, const PathEls::PathComponent &, const T &)> &elWrapper,
+ ListOptions options)
{
if (options == ListOptions::Reverse) {
return List(
- pathFromOwner,
- [&list, elWrapper](const DomItem &self, index_type i) {
- if (i < 0 || i >= list.length())
- return DomItem();
- return elWrapper(self, Path::index(i), list[list.length() -i - 1]);
- }, [&list](const DomItem &) {
- return list.length();
- }, nullptr, QLatin1String(typeid(T).name()));
+ pathFromOwner,
+ [&list, elWrapper](const DomItem &self, index_type i) {
+ if (i < 0 || i >= list.size())
+ return DomItem();
+ return elWrapper(self, PathEls::Index(i), list[list.size() - i - 1]);
+ },
+ [&list](const DomItem &) { return list.size(); }, nullptr,
+ QLatin1String(typeid(T).name()));
} else {
- return List(pathFromOwner,
- [&list, elWrapper](const DomItem &self, index_type i) {
- if (i < 0 || i >= list.length())
- return DomItem();
- return elWrapper(self, Path::index(i), list[i]);
- }, [&list](const DomItem &) {
- return list.length();
- }, nullptr, QLatin1String(typeid(T).name()));
+ return List(
+ pathFromOwner,
+ [&list, elWrapper](const DomItem &self, index_type i) {
+ if (i < 0 || i >= list.size())
+ return DomItem();
+ return elWrapper(self, PathEls::Index(i), list[i]);
+ },
+ [&list](const DomItem &) { return list.size(); }, nullptr,
+ QLatin1String(typeid(T).name()));
}
}
class QMLDOM_EXPORT OwningItem: public DomBase {
protected:
- virtual std::shared_ptr<OwningItem> doCopy(const DomItem &self) = 0;
+ virtual std::shared_ptr<OwningItem> doCopy(const DomItem &self) const = 0;
+
public:
OwningItem(const OwningItem &o);
OwningItem(int derivedFrom=0);
- OwningItem(int derivedFrom, QDateTime lastDataUpdateAt);
+ OwningItem(int derivedFrom, const QDateTime &lastDataUpdateAt);
+ OwningItem(const OwningItem &&) = delete;
+ OwningItem &operator=(const OwningItem &&) = delete;
static int nextRevision();
Path canonicalPath(const DomItem &self) const override = 0;
- bool iterateDirectSubpaths(DomItem &self, std::function<bool (Path, DomItem &)>) override;
- std::shared_ptr<OwningItem> makeCopy(const DomItem &self) {
- return doCopy(self);
- }
- Path pathFromOwner(const DomItem &self) const override;
+ bool iterateDirectSubpaths(const DomItem &self, DirectVisitor) const override;
+ std::shared_ptr<OwningItem> makeCopy(const DomItem &self) const { return doCopy(self); }
+ Path pathFromOwner() const { return Path(); }
+ Path pathFromOwner(const DomItem &) const override final { return Path(); }
DomItem containingObject(const DomItem &self) const override;
int derivedFrom() const;
virtual int revision() const;
@@ -740,18 +1530,20 @@ public:
virtual bool freeze();
QDateTime frozenAt() const;
- virtual void addError(const DomItem &self, ErrorMessage msg);
- void addErrorLocal(ErrorMessage msg);
- void clearErrors(ErrorGroups groups = ErrorGroups({}));
+ virtual void addError(const DomItem &self, ErrorMessage &&msg);
+ void addErrorLocal(ErrorMessage &&msg);
+ void clearErrors(const ErrorGroups &groups = ErrorGroups({}));
// return false if a quick exit was requested
- bool iterateErrors(const DomItem &self, std::function<bool(DomItem source, ErrorMessage msg)> visitor, Path inPath = Path());
+ bool iterateErrors(
+ const DomItem &self,
+ function_ref<bool(const DomItem &source, const ErrorMessage &msg)> visitor,
+ const Path &inPath = Path());
QMultiMap<Path, ErrorMessage> localErrors() const {
QMutexLocker l(mutex());
return m_errors;
}
-
- virtual bool iterateSubOwners(const DomItem &self, std::function<bool(const DomItem &owner)> visitor);
+ virtual bool iterateSubOwners(const DomItem &self, function_ref<bool(const DomItem &owner)> visitor);
QBasicMutex *mutex() const { return &m_mutex; }
private:
@@ -762,164 +1554,90 @@ private:
QDateTime m_lastDataUpdateAt;
QDateTime m_frozenAt;
QMultiMap<Path, ErrorMessage> m_errors;
+ QMap<ErrorMessage, quint32> m_errorsCounts;
};
-template <typename T>
-std::shared_ptr<T> DomItem::ownerAs() const {
- if (m_owner && m_owner->kind() == T::kindValue)
- return std::static_pointer_cast<T>(m_owner);
- return nullptr;
-}
-
-template <typename T>
-SimpleObjectWrap SimpleObjectWrap::fromDataObject(
- Path pathFromOwner, T const & val,
- std::function<QCborValue(T const &)> toData,
- const SourceLocation &loc,
- DomType kind,
- DomKind domKind,
- QString typeName)
-{
- QString objectName;
- if (!typeName.isEmpty())
- objectName = typeName;
- else if (kind != kindValue)
- objectName = domTypeToStringMap()[kind];
- else
- objectName = QLatin1String("SimpleObjectWrap<%1>").arg(QLatin1String(typeid(T).name()));
- return SimpleObjectWrap(
- pathFromOwner, QVariant::fromValue(&val),
- [toData, pathFromOwner](DomItem &self, QVariant v, std::function<bool(Path, DomItem &)> visitor){
- ConstantData data = ConstantData(pathFromOwner, toData(*v.value<T const*>()), ConstantData::Options::FirstMapIsFields);
- return data.iterateDirectSubpaths(self, visitor);
- }, kind, domKind, objectName, loc);
+template<typename T>
+std::shared_ptr<T> DomItem::ownerAs() const
+{
+ if constexpr (domTypeIsOwningItem(T::kindValue)) {
+ if (!std::holds_alternative<std::monostate>(m_owner)) {
+ if constexpr (T::kindValue == DomType::AttachedInfo) {
+ if (std::holds_alternative<std::shared_ptr<AttachedInfo>>(m_owner))
+ return std::static_pointer_cast<T>(
+ std::get<std::shared_ptr<AttachedInfo>>(m_owner));
+ } else if constexpr (T::kindValue == DomType::ExternalItemInfo) {
+ if (std::holds_alternative<std::shared_ptr<ExternalItemInfoBase>>(m_owner))
+ return std::static_pointer_cast<T>(
+ std::get<std::shared_ptr<ExternalItemInfoBase>>(m_owner));
+ } else if constexpr (T::kindValue == DomType::ExternalItemPair) {
+ if (std::holds_alternative<std::shared_ptr<ExternalItemPairBase>>(m_owner))
+ return std::static_pointer_cast<T>(
+ std::get<std::shared_ptr<ExternalItemPairBase>>(m_owner));
+ } else {
+ if (std::holds_alternative<std::shared_ptr<T>>(m_owner)) {
+ return std::get<std::shared_ptr<T>>(m_owner);
+ }
+ }
+ }
+ } else {
+ Q_ASSERT_X(false, "DomItem::ownerAs", "unexpected non owning value in ownerAs");
+ }
+ return std::shared_ptr<T> {};
}
-template <typename T>
-SimpleObjectWrap SimpleObjectWrap::fromObjectRef(
- Path pathFromOwner, T &value,
- std::function<bool(DomItem &, T &val, std::function<bool(Path, DomItem &)>)> directSubpathsIterate,
- const SourceLocation &loc,
- DomType kind,
- QString typeName,
- DomKind domKind)
-{
- return SimpleObjectWrap(
- pathFromOwner, QVariant::fromValue(&value),
- [directSubpathsIterate](DomItem &self, QVariant v, std::function<bool(Path, DomItem &)> visitor){
- return directSubpathsIterate(self, *v.value<T *>(), visitor);
- },
- kind, domKind,
- ((!typeName.isEmpty()) ? typeName :
- (kind != kindValue) ? domTypeToStringMap()[kind] :
- QStringLiteral(u"SimpleObjectWrap<%1>").arg(QLatin1String(typeid(T).name()))),
- loc);
-}
+template<int I>
+struct rank : rank<I - 1>
+{
+ static_assert(I > 0, "");
+};
+template<>
+struct rank<0>
+{
+};
-template <typename T>
-Subpath DomItem::subWrapPath(Path p, T &obj, SourceLocation loc) const {
- return this->subObjectWrap(SimpleObjectWrap::fromObjectRef<T>(
- this->pathFromOwner().subPath(p),
- obj,
- [](DomItem &self, T &fDef, std::function<bool(Path, DomItem &)> visitor) {
- return fDef.iterateDirectSubpaths(self, visitor);
- },loc,
- T::kindValue));
+template<typename T>
+auto writeOutWrap(const T &t, const DomItem &self, OutWriter &lw, rank<1>)
+ -> decltype(t.writeOut(self, lw))
+{
+ t.writeOut(self, lw);
}
-template <typename T>
-Subpath DomItem::subWrapField(QString p, T &obj, SourceLocation loc) const {
- return this->subWrapPath<T>(Path::field(p), obj, loc);
-}
-template <typename T>
-Subpath DomItem::subWrapField(QStringView p, T &obj, SourceLocation loc) const {
- return this->subWrapPath<T>(Path::field(p), obj, loc);
-}
-template <typename T>
-Subpath DomItem::subWrapKey(QString p, T &obj, SourceLocation loc) const {
- return this->subWrapPath<T>(Path::key(p), obj, loc);
-}
-template <typename T>
-Subpath DomItem::subWrapKey(QStringView p, T &obj, SourceLocation loc) const {
- return this->subWrapPath<T>(Path::key(p), obj, loc);
+template<typename T>
+auto writeOutWrap(const T &, const DomItem &, OutWriter &, rank<0>) -> void
+{
+ qCWarning(writeOutLog) << "Ignoring writeout to wrapped object not supporting it ("
+ << typeid(T).name();
}
-template <typename T>
-Subpath DomItem::subWrapIndex(index_type i, T &obj, SourceLocation loc) const {
- return this->subWrapPath<T>(Path::index(i), obj, loc);
+template<typename T>
+auto writeOutWrap(const T &t, const DomItem &self, OutWriter &lw) -> void
+{
+ writeOutWrap(t, self, lw, rank<1>());
}
-// mainly for debugging purposes
-class GenericObject: public DomElement {
-public:
- constexpr static DomType kindValue = DomType::GenericObject;
- DomType kind() const override { return kindValue; }
-
- GenericObject(Path pathFromOwner = Path(), const SourceLocation & loc = SourceLocation(),
- QMap<QString, GenericObject> subObjects = {},
- QMap<QString, QCborValue> subValues = {}):
- DomElement(pathFromOwner, loc), subObjects(subObjects), subValues(subValues) {}
-
- GenericObject copy() const;
- std::pair<QString, GenericObject> asStringPair() const;
-
- bool iterateDirectSubpaths(DomItem &self, std::function<bool (Path, DomItem &)>) override;
-
- QMap<QString, GenericObject> subObjects;
- QMap<QString, QCborValue> subValues;
-};
-
-// mainly for debugging purposes
-class GenericOwner: public OwningItem {
-protected:
- std::shared_ptr<OwningItem> doCopy(const DomItem &self) override;
-public:
- constexpr static DomType kindValue = DomType::GenericOwner;
- DomType kind() const override { return kindValue; }
-
- GenericOwner(Path pathFromTop = Path(), int derivedFrom = 0,
- QMap<QString, GenericObject> subObjects = {},
- QMap<QString, QCborValue> subValues = {}):
- OwningItem(derivedFrom), pathFromTop(pathFromTop), subObjects(subObjects),
- subValues(subValues)
- {}
-
- GenericOwner(Path pathFromTop, int derivedFrom, QDateTime dataRefreshedAt,
- QMap<QString, GenericObject> subObjects = {},
- QMap<QString, QCborValue> subValues = {}):
- OwningItem(derivedFrom, dataRefreshedAt), pathFromTop(pathFromTop), subObjects(subObjects),
- subValues(subValues)
- {}
-
- GenericOwner(const GenericOwner &o);
-
- std::shared_ptr<GenericOwner> makeCopy(const DomItem &self);
- Path canonicalPath(const DomItem &self) const override;
-
- bool iterateDirectSubpaths(DomItem &self, std::function<bool (Path, DomItem &)>) override;
-
- Path pathFromTop;
- QMap<QString, GenericObject> subObjects;
- QMap<QString, QCborValue> subValues;
-};
-
-QDebug operator<<(QDebug debug, const DomItem &c);
+template<typename T>
+void SimpleObjectWrapT<T>::writeOut(const DomItem &self, OutWriter &lw) const
+{
+ writeOutWrap<T>(*asT(), self, lw);
+}
+QMLDOM_EXPORT QDebug operator<<(QDebug debug, const DomItem &c);
-class MutableDomItem {
+class QMLDOM_EXPORT MutableDomItem {
public:
- operator bool() const {
- return m_owner && base();
- }
- DomType internalKind() const {
- return base().internalKind();
- }
- DomKind domKind() const { return kind2domKind(internalKind()); }
+ using CopyOption = DomItem::CopyOption;
- Path canonicalPath() const
+ explicit operator bool() const
+ {
+ return bool(m_owner);
+ } // this is weaker than item(), but normally correct
+ DomType internalKind() { return item().internalKind(); }
+ QString internalKindStr() { return domTypeToString(internalKind()); }
+ DomKind domKind() { return kind2domKind(internalKind()); }
+
+ Path canonicalPath() const { return m_owner.canonicalPath().path(m_pathFromOwner); }
+ MutableDomItem containingObject()
{
- return m_owner.canonicalPath().subPath(m_pathFromOwner);
- }
- MutableDomItem containingObject() const {
if (m_pathFromOwner)
return MutableDomItem(m_owner, m_pathFromOwner.split().pathToSource);
else {
@@ -928,170 +1646,229 @@ public:
}
}
- MutableDomItem container() const {
+ MutableDomItem container()
+ {
if (m_pathFromOwner)
return MutableDomItem(m_owner, m_pathFromOwner.dropTail());
else {
- return MutableDomItem(base().container());
+ return MutableDomItem(item().container());
}
}
- MutableDomItem component() const {
- return MutableDomItem{base().component()};
- }
- MutableDomItem owner() const {
- return MutableDomItem(m_owner);
- }
- MutableDomItem top() const {
- return MutableDomItem(base().top());
- }
- MutableDomItem environment() const {
- return MutableDomItem(base().environment());
- }
- MutableDomItem universe() const {
- return MutableDomItem(base().universe());
+ MutableDomItem qmlObject(GoTo option = GoTo::Strict,
+ FilterUpOptions fOptions = FilterUpOptions::ReturnOuter)
+ {
+ return MutableDomItem(item().qmlObject(option, fOptions));
}
- Path pathFromOwner() const {
- return m_pathFromOwner;
+ MutableDomItem fileObject(GoTo option = GoTo::Strict)
+ {
+ return MutableDomItem(item().fileObject(option));
}
- MutableDomItem operator[](const Path &path) const {
- return MutableDomItem(base()[path]);
+ MutableDomItem rootQmlObject(GoTo option = GoTo::Strict)
+ {
+ return MutableDomItem(item().rootQmlObject(option));
}
- MutableDomItem operator[](QStringView component) const {
- return MutableDomItem(base()[component]);
+ MutableDomItem globalScope() { return MutableDomItem(item().globalScope()); }
+ MutableDomItem scope() { return MutableDomItem(item().scope()); }
+
+ MutableDomItem component(GoTo option = GoTo::Strict)
+ {
+ return MutableDomItem { item().component(option) };
}
- MutableDomItem operator[](const QString &component) const {
- return MutableDomItem(base()[component]);
+ MutableDomItem owner() { return MutableDomItem(m_owner); }
+ MutableDomItem top() { return MutableDomItem(item().top()); }
+ MutableDomItem environment() { return MutableDomItem(item().environment()); }
+ MutableDomItem universe() { return MutableDomItem(item().universe()); }
+ Path pathFromOwner() { return m_pathFromOwner; }
+ MutableDomItem operator[](const Path &path) { return MutableDomItem(item()[path]); }
+ MutableDomItem operator[](QStringView component) { return MutableDomItem(item()[component]); }
+ MutableDomItem operator[](const QString &component)
+ {
+ return MutableDomItem(item()[component]);
}
- MutableDomItem operator[](const char16_t *component) const {
+ MutableDomItem operator[](const char16_t *component)
+ {
// to avoid clash with stupid builtin ptrdiff_t[MutableDomItem&], coming from C
- return MutableDomItem(base()[QStringView(component)]);
- }
- MutableDomItem operator[](index_type i) const {
- return MutableDomItem(base().index(i));
+ return MutableDomItem(item()[QStringView(component)]);
}
+ MutableDomItem operator[](index_type i) { return MutableDomItem(item().index(i)); }
- MutableDomItem path(const Path &p) const {
- return MutableDomItem(base().path(p));
- }
- MutableDomItem path(const QString &p) const {
- return path(Path::fromString(p));
- }
- MutableDomItem path(QStringView p) const {
- return path(Path::fromString(p));
- }
+ MutableDomItem path(const Path &p) { return MutableDomItem(item().path(p)); }
+ MutableDomItem path(const QString &p) { return path(Path::fromString(p)); }
+ MutableDomItem path(QStringView p) { return path(Path::fromString(p)); }
- QList<QString> const fields() const {
- return base().fields();
- }
- MutableDomItem field(QStringView name) const {
- return MutableDomItem(base().field(name));
- }
- index_type indexes() const {
- return base().indexes();
- }
- MutableDomItem index(index_type i) const {
- return MutableDomItem(base().index(i));
- }
-
- QSet<QString> const keys() const {
- return base().keys();
- }
- MutableDomItem key(QString name) const {
- return MutableDomItem(base().key(name));
- }
+ QList<QString> const fields() { return item().fields(); }
+ MutableDomItem field(QStringView name) { return MutableDomItem(item().field(name)); }
+ index_type indexes() { return item().indexes(); }
+ MutableDomItem index(index_type i) { return MutableDomItem(item().index(i)); }
- QString canonicalFilePath() const { return base().canonicalFilePath(); }
- SourceLocation location() const { return base().location(); }
+ QSet<QString> const keys() { return item().keys(); }
+ MutableDomItem key(const QString &name) { return MutableDomItem(item().key(name)); }
+ MutableDomItem key(QStringView name) { return key(name.toString()); }
- QCborValue value() const {
- return base().value();
+ void
+ dump(const Sink &s, int indent = 0,
+ function_ref<bool(const DomItem &, const PathEls::PathComponent &, const DomItem &)> filter = noFilter)
+ {
+ item().dump(s, indent, filter);
}
-
- void dump(Sink sink, int indent = 0) const {
- return base().dump(sink, indent);
+ FileWriter::Status
+ dump(const QString &path,
+ function_ref<bool(const DomItem &, const PathEls::PathComponent &, const DomItem &)> filter = noFilter,
+ int nBackups = 2, int indent = 0, FileWriter *fw = nullptr)
+ {
+ return item().dump(path, filter, nBackups, indent, fw);
}
- QString toString() const {
- return base().toString();
+ void writeOut(OutWriter &lw) { return item().writeOut(lw); }
+ bool writeOut(const QString &path, int nBackups = 2,
+ const LineWriterOptions &opt = LineWriterOptions(), FileWriter *fw = nullptr)
+ {
+ return item().writeOut(path, nBackups, opt, fw);
}
- // convenience getters
- QString name() const;
- MutableDomItem qmlChildren() const {
- return MutableDomItem(base().qmlChildren());
+ MutableDomItem fileLocations() { return MutableDomItem(item().fileLocations()); }
+ MutableDomItem makeCopy(CopyOption option = CopyOption::EnvConnected)
+ {
+ return item().makeCopy(option);
}
- MutableDomItem annotations() const {
- return MutableDomItem(base().annotations());
+ bool commitToBase(const std::shared_ptr<DomEnvironment> &validEnvPtr = nullptr)
+ {
+ return item().commitToBase(validEnvPtr);
}
+ QString canonicalFilePath() const { return item().canonicalFilePath(); }
- QMultiMap<QString, RequiredProperty> extraRequired() const;
+ MutableDomItem refreshed() { return MutableDomItem(item().refreshed()); }
+ QCborValue value() { return item().value(); }
-// // OwnigItem elements
- int derivedFrom() const {
- return m_owner.derivedFrom();
- }
- int revision() const {
- return m_owner.revision();
- }
- QDateTime createdAt() const {
- return m_owner.createdAt();
- }
- QDateTime frozenAt() const {
- return m_owner.frozenAt();
- }
- QDateTime lastDataUpdateAt() const {
- return m_owner.lastDataUpdateAt();
- }
+ QString toString() { return item().toString(); }
- void addError(ErrorMessage msg) const {
- base().addError(msg);
- }
- ErrorHandler errorHandler() const;
+ // convenience getters
+ QString name() { return item().name(); }
+ MutableDomItem pragmas() { return item().pragmas(); }
+ MutableDomItem ids() { return MutableDomItem::item().ids(); }
+ QString idStr() { return item().idStr(); }
+ MutableDomItem propertyDefs() { return MutableDomItem(item().propertyDefs()); }
+ MutableDomItem bindings() { return MutableDomItem(item().bindings()); }
+ MutableDomItem methods() { return MutableDomItem(item().methods()); }
+ MutableDomItem children() { return MutableDomItem(item().children()); }
+ MutableDomItem child(index_type i) { return MutableDomItem(item().child(i)); }
+ MutableDomItem annotations() { return MutableDomItem(item().annotations()); }
+
+ // // OwnigItem elements
+ int derivedFrom() { return m_owner.derivedFrom(); }
+ int revision() { return m_owner.revision(); }
+ QDateTime createdAt() { return m_owner.createdAt(); }
+ QDateTime frozenAt() { return m_owner.frozenAt(); }
+ QDateTime lastDataUpdateAt() { return m_owner.lastDataUpdateAt(); }
+
+ void addError(ErrorMessage &&msg) { item().addError(std::move(msg)); }
+ ErrorHandler errorHandler();
+
+ // convenience setters
+ MutableDomItem addPrototypePath(const Path &prototypePath);
+ MutableDomItem setNextScopePath(const Path &nextScopePath);
+ MutableDomItem setPropertyDefs(QMultiMap<QString, PropertyDefinition> propertyDefs);
+ MutableDomItem setBindings(QMultiMap<QString, Binding> bindings);
+ MutableDomItem setMethods(QMultiMap<QString, MethodInfo> functionDefs);
+ MutableDomItem setChildren(const QList<QmlObject> &children);
+ MutableDomItem setAnnotations(const QList<QmlObject> &annotations);
+ MutableDomItem setScript(const std::shared_ptr<ScriptExpression> &exp);
+ MutableDomItem setCode(const QString &code);
+ MutableDomItem addPropertyDef(const PropertyDefinition &propertyDef,
+ AddOption option = AddOption::Overwrite);
+ MutableDomItem addBinding(Binding binding, AddOption option = AddOption::Overwrite);
+ MutableDomItem addMethod(
+ const MethodInfo &functionDef, AddOption option = AddOption::Overwrite);
+ MutableDomItem addChild(QmlObject child);
+ MutableDomItem addAnnotation(QmlObject child);
+ MutableDomItem addPreComment(const Comment &comment, FileLocationRegion region);
+ MutableDomItem addPostComment(const Comment &comment, FileLocationRegion region);
+ QQmlJSScope::ConstPtr semanticScope();
+ void setSemanticScope(const QQmlJSScope::ConstPtr &scope);
MutableDomItem() = default;
- MutableDomItem(DomItem owner, Path pathFromOwner):
+ MutableDomItem(const DomItem &owner, const Path &pathFromOwner):
m_owner(owner), m_pathFromOwner(pathFromOwner)
{}
- MutableDomItem(DomItem item):
+ MutableDomItem(const DomItem &item):
m_owner(item.owner()), m_pathFromOwner(item.pathFromOwner())
{}
- std::shared_ptr<DomTop> topPtr() const {
- return m_owner.topPtr();
- }
- std::shared_ptr<OwningItem> owningItemPtr() const {
- return m_owner.owningItemPtr();
- }
+ std::shared_ptr<DomTop> topPtr() { return m_owner.topPtr(); }
+ std::shared_ptr<OwningItem> owningItemPtr() { return m_owner.owningItemPtr(); }
- template <typename T>
- T const*as() const {
- return base().as<T>();
+ template<typename T>
+ T const *as()
+ {
+ return item().as<T>();
}
template <typename T>
T *mutableAs() {
- Q_ASSERT(!m_owner.owningItemPtr()->frozen());
- return base().mutableAs<T>();
+ Q_ASSERT(!m_owner || !m_owner.owningItemPtr()->frozen());
+
+ DomItem self = item();
+ if (self.m_kind != T::kindValue)
+ return nullptr;
+
+ const T *t = nullptr;
+ if constexpr (domTypeIsObjWrap(T::kindValue) || domTypeIsValueWrap(T::kindValue))
+ t = static_cast<const SimpleObjectWrapBase *>(self.base())->as<T>();
+ else if constexpr (std::is_base_of<DomBase, T>::value)
+ t = static_cast<const T *>(self.base());
+ else
+ Q_UNREACHABLE_RETURN(nullptr);
+
+ // Nasty. But since ElementT has to store the const pointers, we allow it in this one place.
+ return const_cast<T *>(t);
}
- template <typename T>
- std::shared_ptr<T> ownerAs() const {
+ template<typename T>
+ std::shared_ptr<T> ownerAs() const
+ {
return m_owner.ownerAs<T>();
}
// it is dangerous to assume it stays valid when updates are preformed...
- DomItem base() const {
- return m_owner.path(m_pathFromOwner);
+ DomItem item() const { return m_owner.path(m_pathFromOwner); }
+
+ friend bool operator==(const MutableDomItem &o1, const MutableDomItem &o2)
+ {
+ return o1.m_owner == o2.m_owner && o1.m_pathFromOwner == o2.m_pathFromOwner;
+ }
+ friend bool operator!=(const MutableDomItem &o1, const MutableDomItem &o2)
+ {
+ return !(o1 == o2);
}
+
private:
DomItem m_owner;
Path m_pathFromOwner;
};
-QDebug operator<<(QDebug debug, const MutableDomItem &c);
+QMLDOM_EXPORT QDebug operator<<(QDebug debug, const MutableDomItem &c);
-template <typename K, typename T>
-Path insertUpdatableElementInMultiMap(Path mapPathFromOwner, QMultiMap<K, T> &mmap, K key, const T&value) {
+template<typename K, typename T>
+Path insertUpdatableElementInMultiMap(const Path &mapPathFromOwner, QMultiMap<K, T> &mmap, K key,
+ const T &value, AddOption option = AddOption::KeepExisting,
+ T **valuePtr = nullptr)
+{
+ if (option == AddOption::Overwrite) {
+ auto it = mmap.find(key);
+ if (it != mmap.end()) {
+ T &v = *it;
+ v = value;
+ if (++it != mmap.end() && it.key() == key) {
+ qWarning() << " requested overwrite of " << key
+ << " that contains aleready multiple entries in" << mapPathFromOwner;
+ }
+ Path newPath = mapPathFromOwner.key(key).index(0);
+ v.updatePathFromOwner(newPath);
+ if (valuePtr)
+ *valuePtr = &v;
+ return newPath;
+ }
+ }
mmap.insert(key, value);
auto it = mmap.find(key);
auto it2 = it;
@@ -1100,24 +1877,30 @@ Path insertUpdatableElementInMultiMap(Path mapPathFromOwner, QMultiMap<K, T> &mm
++nVal;
++it2;
}
- Path newPath = mapPathFromOwner.subKey(key).subIndex(nVal-1);
- T &newComp = *it;
- newComp.updatePathFromOwner(newPath);
+ Path newPath = mapPathFromOwner.key(key).index(nVal-1);
+ T &v = *it;
+ v.updatePathFromOwner(newPath);
+ if (valuePtr)
+ *valuePtr = &v;
return newPath;
}
-template <typename T>
-Path appendUpdatableElementInQList(Path listPathFromOwner, QList<T> &list, const T&value) {
- int idx = list.length();
+template<typename T>
+Path appendUpdatableElementInQList(const Path &listPathFromOwner, QList<T> &list, const T &value,
+ T **vPtr = nullptr)
+{
+ int idx = list.size();
list.append(value);
- Path newPath = listPathFromOwner.subIndex(idx);
- list[idx].updatePathFromOwner(newPath);
+ Path newPath = listPathFromOwner.index(idx);
+ T &targetV = list[idx];
+ targetV.updatePathFromOwner(newPath);
+ if (vPtr)
+ *vPtr = &targetV;
return newPath;
}
-
template <typename T, typename K = QString>
-void updatePathFromOwnerMultiMap(QMultiMap<K, T> &mmap, Path newPath)
+void updatePathFromOwnerMultiMap(QMultiMap<K, T> &mmap, const Path &newPath)
{
auto it = mmap.begin();
auto end = mmap.end();
@@ -1126,9 +1909,9 @@ void updatePathFromOwnerMultiMap(QMultiMap<K, T> &mmap, Path newPath)
QList<T*> els;
while (it != end) {
if (i > 0 && name != it.key()) {
- Path pName = newPath.subKey(QString(name));
- foreach (T *el, els)
- el->updatePathFromOwner(pName.subIndex(--i));
+ Path pName = newPath.key(QString(name));
+ for (T *el : els)
+ el->updatePathFromOwner(pName.index(--i));
els.clear();
els.append(&(*it));
name = it.key();
@@ -1140,19 +1923,413 @@ void updatePathFromOwnerMultiMap(QMultiMap<K, T> &mmap, Path newPath)
}
++it;
}
- Path pName = newPath.subKey(name);
- foreach (T *el, els)
- el->updatePathFromOwner(pName.subIndex(--i));
+ Path pName = newPath.key(name);
+ for (T *el : els)
+ el->updatePathFromOwner(pName.index(--i));
}
template <typename T>
-void updatePathFromOwnerQList(QList<T> &list, Path newPath)
+void updatePathFromOwnerQList(QList<T> &list, const Path &newPath)
{
auto it = list.begin();
auto end = list.end();
index_type i = 0;
while (it != end)
- (it++)->updatePathFromOwner(newPath.subIndex(i++));
+ (it++)->updatePathFromOwner(newPath.index(i++));
+}
+
+constexpr bool domTypeIsObjWrap(DomType k)
+{
+ switch (k) {
+ case DomType::Binding:
+ case DomType::EnumItem:
+ case DomType::ErrorMessage:
+ case DomType::Export:
+ case DomType::Id:
+ case DomType::Import:
+ case DomType::ImportScope:
+ case DomType::MethodInfo:
+ case DomType::MethodParameter:
+ case DomType::ModuleAutoExport:
+ case DomType::Pragma:
+ case DomType::PropertyDefinition:
+ case DomType::Version:
+ case DomType::Comment:
+ case DomType::CommentedElement:
+ case DomType::RegionComments:
+ case DomType::FileLocations:
+ case DomType::UpdatedScriptExpression:
+ return true;
+ default:
+ return false;
+ }
+}
+
+constexpr bool domTypeIsValueWrap(DomType k)
+{
+ switch (k) {
+ case DomType::PropertyInfo:
+ return true;
+ default:
+ return false;
+ }
+}
+
+constexpr bool domTypeIsDomElement(DomType k)
+{
+ switch (k) {
+ case DomType::ModuleScope:
+ case DomType::QmlObject:
+ case DomType::ConstantData:
+ case DomType::SimpleObjectWrap:
+ case DomType::Reference:
+ case DomType::Map:
+ case DomType::List:
+ case DomType::ListP:
+ case DomType::EnumDecl:
+ case DomType::JsResource:
+ case DomType::QmltypesComponent:
+ case DomType::QmlComponent:
+ case DomType::GlobalComponent:
+ case DomType::MockObject:
+ return true;
+ default:
+ return false;
+ }
+}
+
+constexpr bool domTypeIsOwningItem(DomType k)
+{
+ switch (k) {
+ case DomType::ModuleIndex:
+
+ case DomType::MockOwner:
+
+ case DomType::ExternalItemInfo:
+ case DomType::ExternalItemPair:
+
+ case DomType::QmlDirectory:
+ case DomType::QmldirFile:
+ case DomType::JsFile:
+ case DomType::QmlFile:
+ case DomType::QmltypesFile:
+ case DomType::GlobalScope:
+
+ case DomType::ScriptExpression:
+ case DomType::AstComments:
+
+ case DomType::LoadInfo:
+ case DomType::AttachedInfo:
+
+ case DomType::DomEnvironment:
+ case DomType::DomUniverse:
+ return true;
+ default:
+ return false;
+ }
+}
+
+constexpr bool domTypeIsUnattachedOwningItem(DomType k)
+{
+ switch (k) {
+ case DomType::ScriptExpression:
+ case DomType::AstComments:
+ case DomType::AttachedInfo:
+ return true;
+ default:
+ return false;
+ }
+}
+
+constexpr bool domTypeIsScriptElement(DomType k)
+{
+ return DomType::ScriptElementStart <= k && k <= DomType::ScriptElementStop;
+}
+
+template<typename T>
+DomItem DomItem::subValueItem(const PathEls::PathComponent &c, const T &value,
+ ConstantData::Options options) const
+{
+ using BaseT = std::remove_cv_t<std::remove_reference_t<T>>;
+ if constexpr (
+ std::is_base_of_v<
+ QCborValue,
+ BaseT> || std::is_base_of_v<QCborArray, BaseT> || std::is_base_of_v<QCborMap, BaseT>) {
+ return DomItem(m_top, m_owner, m_ownerPath,
+ ConstantData(pathFromOwner().appendComponent(c), value, options));
+ } else if constexpr (std::is_same_v<DomItem, BaseT>) {
+ Q_UNUSED(options);
+ return value;
+ } else if constexpr (IsList<T>::value && !std::is_convertible_v<BaseT, QStringView>) {
+ return subListItem(List::fromQList<typename BaseT::value_type>(
+ pathFromOwner().appendComponent(c), value,
+ [options](const DomItem &list, const PathEls::PathComponent &p,
+ const typename T::value_type &v) { return list.subValueItem(p, v, options); }));
+ } else if constexpr (IsSharedPointerToDomObject<BaseT>::value) {
+ Q_UNUSED(options);
+ return subOwnerItem(c, value);
+ } else {
+ return subDataItem(c, value, options);
+ }
+}
+
+template<typename T>
+DomItem DomItem::subDataItem(const PathEls::PathComponent &c, const T &value,
+ ConstantData::Options options) const
+{
+ using BaseT = std::remove_cv_t<std::remove_reference_t<T>>;
+ if constexpr (std::is_same_v<BaseT, ConstantData>) {
+ return this->copy(value);
+ } else if constexpr (std::is_base_of_v<QCborValue, BaseT>) {
+ return DomItem(m_top, m_owner, m_ownerPath,
+ ConstantData(pathFromOwner().appendComponent(c), value, options));
+ } else {
+ return DomItem(
+ m_top, m_owner, m_ownerPath,
+ ConstantData(pathFromOwner().appendComponent(c), QCborValue(value), options));
+ }
+}
+
+template<typename T>
+bool DomItem::dvValue(DirectVisitor visitor, const PathEls::PathComponent &c, const T &value,
+ ConstantData::Options options) const
+{
+ auto lazyWrap = [this, &c, &value, options]() {
+ return this->subValueItem<T>(c, value, options);
+ };
+ return visitor(c, lazyWrap);
+}
+
+template<typename F>
+bool DomItem::dvValueLazy(DirectVisitor visitor, const PathEls::PathComponent &c, F valueF,
+ ConstantData::Options options) const
+{
+ auto lazyWrap = [this, &c, &valueF, options]() {
+ return this->subValueItem<decltype(valueF())>(c, valueF(), options);
+ };
+ return visitor(c, lazyWrap);
+}
+
+template<typename T>
+DomItem DomItem::wrap(const PathEls::PathComponent &c, const T &obj) const
+{
+ using BaseT = std::decay_t<T>;
+ if constexpr (std::is_same_v<QString, BaseT> || std::is_arithmetic_v<BaseT>) {
+ return this->subDataItem(c, QCborValue(obj));
+ } else if constexpr (std::is_same_v<SourceLocation, BaseT>) {
+ return this->subLocationItem(c, obj);
+ } else if constexpr (std::is_same_v<BaseT, Reference>) {
+ Q_ASSERT_X(false, "DomItem::wrap",
+ "wrapping a reference object, probably an error (wrap the target path instead)");
+ return this->copy(obj);
+ } else if constexpr (std::is_same_v<BaseT, ConstantData>) {
+ return this->subDataItem(c, obj);
+ } else if constexpr (std::is_same_v<BaseT, Map>) {
+ return this->subMapItem(obj);
+ } else if constexpr (std::is_same_v<BaseT, List>) {
+ return this->subListItem(obj);
+ } else if constexpr (std::is_base_of_v<ListPBase, BaseT>) {
+ return this->subListItem(obj);
+ } else if constexpr (std::is_same_v<BaseT, SimpleObjectWrap>) {
+ return this->subObjectWrapItem(obj);
+ } else if constexpr (IsDomObject<BaseT>::value) {
+ if constexpr (domTypeIsObjWrap(BaseT::kindValue) || domTypeIsValueWrap(BaseT::kindValue)) {
+ return this->subObjectWrapItem(
+ SimpleObjectWrap::fromObjectRef(this->pathFromOwner().appendComponent(c), obj));
+ } else if constexpr (domTypeIsDomElement(BaseT::kindValue)) {
+ return this->copy(&obj);
+ } else {
+ qCWarning(domLog) << "Unhandled object of type " << domTypeToString(BaseT::kindValue)
+ << " in DomItem::wrap, not using a shared_ptr for an "
+ << "OwningItem, or unexpected wrapped object?";
+ return DomItem();
+ }
+ } else if constexpr (IsSharedPointerToDomObject<BaseT>::value) {
+ if constexpr (domTypeIsOwningItem(BaseT::element_type::kindValue)) {
+ return this->subOwnerItem(c, obj);
+ } else {
+ Q_ASSERT_X(false, "DomItem::wrap", "shared_ptr with non owning item");
+ return DomItem();
+ }
+ } else if constexpr (IsMultiMap<BaseT>::value) {
+ if constexpr (std::is_same_v<typename BaseT::key_type, QString>) {
+ return subMapItem(Map::fromMultiMapRef<typename BaseT::mapped_type>(
+ pathFromOwner().appendComponent(c), obj));
+ } else {
+ Q_ASSERT_X(false, "DomItem::wrap", "non string keys not supported (try .toString()?)");
+ }
+ } else if constexpr (IsMap<BaseT>::value) {
+ if constexpr (std::is_same_v<typename BaseT::key_type, QString>) {
+ return subMapItem(Map::fromMapRef<typename BaseT::mapped_type>(
+ pathFromOwner().appendComponent(c), obj,
+ [](const DomItem &map, const PathEls::PathComponent &p,
+ const typename BaseT::mapped_type &el) { return map.wrap(p, el); }));
+ } else {
+ Q_ASSERT_X(false, "DomItem::wrap", "non string keys not supported (try .toString()?)");
+ }
+ } else if constexpr (IsList<BaseT>::value) {
+ if constexpr (IsDomObject<typename BaseT::value_type>::value) {
+ return subListItem(List::fromQListRef<typename BaseT::value_type>(
+ pathFromOwner().appendComponent(c), obj,
+ [](const DomItem &list, const PathEls::PathComponent &p,
+ const typename BaseT::value_type &el) { return list.wrap(p, el); }));
+ } else {
+ Q_ASSERT_X(false, "DomItem::wrap", "Unsupported list type T");
+ return DomItem();
+ }
+ } else {
+ qCWarning(domLog) << "Cannot wrap " << typeid(BaseT).name();
+ Q_ASSERT_X(false, "DomItem::wrap", "Do not know how to wrap type T");
+ return DomItem();
+ }
+}
+
+template<typename T>
+bool DomItem::dvWrap(DirectVisitor visitor, const PathEls::PathComponent &c, T &obj) const
+{
+ auto lazyWrap = [this, &c, &obj]() { return this->wrap<T>(c, obj); };
+ return visitor(c, lazyWrap);
+}
+
+template<typename T>
+bool ListPT<T>::iterateDirectSubpaths(const DomItem &self, DirectVisitor v) const
+{
+ index_type len = index_type(m_pList.size());
+ for (index_type i = 0; i < len; ++i) {
+ if (!v(PathEls::Index(i), [this, &self, i] { return this->index(self, i); }))
+ return false;
+ }
+ return true;
+}
+
+template<typename T>
+DomItem ListPT<T>::index(const DomItem &self, index_type index) const
+{
+ if (index >= 0 && index < m_pList.size())
+ return self.wrap(PathEls::Index(index), *static_cast<const T *>(m_pList.value(index)));
+ return DomItem();
+}
+
+// allow inlining of DomBase
+inline DomKind DomBase::domKind() const
+{
+ return kind2domKind(kind());
+}
+
+inline bool DomBase::iterateDirectSubpathsConst(const DomItem &self, DirectVisitor visitor) const
+{
+ Q_ASSERT(self.base() == this);
+ return self.iterateDirectSubpaths(std::move(visitor));
+}
+
+inline DomItem DomBase::containingObject(const DomItem &self) const
+{
+ Path path = pathFromOwner(self);
+ DomItem base = self.owner();
+ if (!path) {
+ path = canonicalPath(self);
+ base = self;
+ }
+ Source source = path.split();
+ return base.path(source.pathToSource);
+}
+
+inline quintptr DomBase::id() const
+{
+ return quintptr(this);
+}
+
+inline QString DomBase::typeName() const
+{
+ return domTypeToString(kind());
+}
+
+inline QList<QString> DomBase::fields(const DomItem &self) const
+{
+ QList<QString> res;
+ self.iterateDirectSubpaths([&res](const PathEls::PathComponent &c, function_ref<DomItem()>) {
+ if (c.kind() == Path::Kind::Field)
+ res.append(c.name());
+ return true;
+ });
+ return res;
+}
+
+inline DomItem DomBase::field(const DomItem &self, QStringView name) const
+{
+ DomItem res;
+ self.iterateDirectSubpaths(
+ [&res, name](const PathEls::PathComponent &c, function_ref<DomItem()> obj) {
+ if (c.kind() == Path::Kind::Field && c.checkName(name)) {
+ res = obj();
+ return false;
+ }
+ return true;
+ });
+ return res;
+}
+
+inline index_type DomBase::indexes(const DomItem &self) const
+{
+ index_type res = 0;
+ self.iterateDirectSubpaths([&res](const PathEls::PathComponent &c, function_ref<DomItem()>) {
+ if (c.kind() == Path::Kind::Index) {
+ index_type i = c.index() + 1;
+ if (res < i)
+ res = i;
+ }
+ return true;
+ });
+ return res;
+}
+
+inline DomItem DomBase::index(const DomItem &self, qint64 index) const
+{
+ DomItem res;
+ self.iterateDirectSubpaths(
+ [&res, index](const PathEls::PathComponent &c, function_ref<DomItem()> obj) {
+ if (c.kind() == Path::Kind::Index && c.index() == index) {
+ res = obj();
+ return false;
+ }
+ return true;
+ });
+ return res;
+}
+
+inline QSet<QString> const DomBase::keys(const DomItem &self) const
+{
+ QSet<QString> res;
+ self.iterateDirectSubpaths([&res](const PathEls::PathComponent &c, function_ref<DomItem()>) {
+ if (c.kind() == Path::Kind::Key)
+ res.insert(c.name());
+ return true;
+ });
+ return res;
+}
+
+inline DomItem DomBase::key(const DomItem &self, const QString &name) const
+{
+ DomItem res;
+ self.iterateDirectSubpaths(
+ [&res, name](const PathEls::PathComponent &c, function_ref<DomItem()> obj) {
+ if (c.kind() == Path::Kind::Key && c.checkName(name)) {
+ res = obj();
+ return false;
+ }
+ return true;
+ });
+ return res;
+}
+
+inline DomItem DomItem::subListItem(const List &list) const
+{
+ return DomItem(m_top, m_owner, m_ownerPath, list);
+}
+
+inline DomItem DomItem::subMapItem(const Map &map) const
+{
+ return DomItem(m_top, m_owner, m_ownerPath, map);
}
} // end namespace Dom
diff --git a/src/qmldom/qqmldomlinewriter.cpp b/src/qmldom/qqmldomlinewriter.cpp
new file mode 100644
index 0000000000..33326cb01a
--- /dev/null
+++ b/src/qmldom/qqmldomlinewriter.cpp
@@ -0,0 +1,456 @@
+// Copyright (C) 2021 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+
+#include "qqmldomlinewriter_p.h"
+#include <QtCore/QCoreApplication>
+#include <QtCore/QRegularExpression>
+
+QT_BEGIN_NAMESPACE
+namespace QQmlJS {
+namespace Dom {
+
+quint32 PendingSourceLocation::utf16Start() const
+{
+ return value.offset;
+}
+
+quint32 PendingSourceLocation::utf16End() const
+{
+ return value.offset + value.length;
+}
+
+void PendingSourceLocation::changeAtOffset(quint32 offset, qint32 change, qint32 colChange,
+ qint32 lineChange)
+{
+ if (offset < utf16Start()) {
+ if (change < 0 && offset - change >= utf16Start()) {
+ int c1 = offset - utf16Start();
+ int c2 = offset - change - utf16Start();
+ change = c1;
+ if (value.length < quint32(c2))
+ value.length = 0;
+ else
+ value.length -= c2;
+ }
+ value.offset += change;
+ value.startColumn += colChange;
+ value.startLine += lineChange;
+ } else if (offset < utf16End()) {
+ if (change < 0 && offset - change > utf16End())
+ change = offset - utf16End();
+ value.length += change;
+ }
+}
+
+void PendingSourceLocation::commit()
+{
+ if (toUpdate)
+ *toUpdate = value;
+ if (updater)
+ updater(value);
+}
+
+LineWriter::LineWriter(
+ const SinkF &innerSink, const QString &fileName, const LineWriterOptions &options,
+ int lineNr, int columnNr, int utf16Offset, const QString &currentLine)
+ : m_innerSinks({ innerSink }),
+ m_fileName(fileName),
+ m_lineNr(lineNr),
+ m_columnNr(columnNr),
+ m_currentColumnNr(columnNr),
+ m_utf16Offset(utf16Offset),
+ m_currentLine(currentLine),
+ m_options(options)
+{
+}
+
+LineWriter &LineWriter::ensureNewline(int nNewline, TextAddType t)
+{
+ int nToAdd = nNewline;
+ if (nToAdd <= 0)
+ return *this;
+ if (m_currentLine.trimmed().isEmpty()) {
+ --nToAdd;
+ if (m_committedEmptyLines >= unsigned(nToAdd))
+ return *this;
+ nToAdd -= m_committedEmptyLines;
+ }
+ for (int i = 0; i < nToAdd; ++i)
+ write(u"\n", t);
+ return *this;
+}
+
+LineWriter &LineWriter::ensureSpace(TextAddType t)
+{
+ if (!m_currentLine.isEmpty() && !m_currentLine.at(m_currentLine.size() - 1).isSpace())
+ write(u" ", t);
+ return *this;
+}
+
+LineWriter &LineWriter::ensureSpace(QStringView space, TextAddType t)
+{
+ int tabSize = m_options.formatOptions.tabSize;
+ IndentInfo ind(space, tabSize);
+ auto cc = counter();
+ if (ind.nNewlines > 0)
+ ensureNewline(ind.nNewlines, t);
+ if (cc != counter() || m_currentLine.isEmpty()
+ || !m_currentLine.at(m_currentLine.size() - 1).isSpace())
+ write(ind.trailingString, t);
+ else {
+ int len = m_currentLine.size();
+ int i = len;
+ while (i != 0 && m_currentLine.at(i - 1).isSpace())
+ --i;
+ QStringView trailingSpace = QStringView(m_currentLine).mid(i, len - i);
+ int trailingSpaceStartColumn =
+ IndentInfo(QStringView(m_currentLine).mid(0, i), tabSize, m_columnNr).column;
+ IndentInfo indExisting(trailingSpace, tabSize, trailingSpaceStartColumn);
+ if (trailingSpaceStartColumn != 0)
+ ind = IndentInfo(space, tabSize, trailingSpaceStartColumn);
+ if (i == 0) {
+ if (indExisting.column < ind.column) {
+ qint32 utf16Change = ind.trailingString.size() - trailingSpace.size();
+ m_currentColumnNr += ind.trailingString.size() - trailingSpace.size();
+ m_currentLine.replace(
+ i, len - i, ind.trailingString.toString()); // invalidates most QStringViews
+ changeAtOffset(i, utf16Change, utf16Change, 0);
+ lineChanged();
+ }
+ } else if (indExisting.column < ind.column) { // use just spaces if not at start of a line
+ write(QStringLiteral(u" ").repeated(ind.column - indExisting.column), t);
+ }
+ }
+ return *this;
+}
+
+QString LineWriter::eolToWrite() const
+{
+ switch (m_options.lineEndings) {
+ case LineWriterOptions::LineEndings::Unix:
+ return QStringLiteral(u"\n");
+ case LineWriterOptions::LineEndings::Windows:
+ return QStringLiteral(u"\r\n");
+ case LineWriterOptions::LineEndings::OldMacOs:
+ return QStringLiteral(u"\r");
+ }
+ Q_ASSERT(false);
+ return QStringLiteral(u"\n");
+}
+
+template<typename String, typename ...Args>
+static QRegularExpressionMatch matchHelper(QRegularExpression &re, String &&s, Args &&...args)
+{
+ return re.matchView(s, args...);
+}
+
+LineWriter &LineWriter::write(QStringView v, TextAddType tAdd)
+{
+ QString eol;
+ // split multiple lines
+ static QRegularExpression eolRe(QLatin1String(
+ "(\r?\n|\r)")); // does not support split of \r and \n for windows style line endings
+ QRegularExpressionMatch m = matchHelper(eolRe, v);
+ if (m.hasMatch()) {
+ // add line by line
+ auto i = m.capturedStart(1);
+ auto iEnd = m.capturedEnd(1);
+ eol = eolToWrite();
+ // offset change (eol used vs input) cannot affect things,
+ // because we cannot have already opened or closed a PendingSourceLocation
+ if (iEnd < v.size()) {
+ write(v.mid(0, iEnd));
+ m = matchHelper(eolRe, v, iEnd);
+ while (m.hasMatch()) {
+ write(v.mid(iEnd, m.capturedEnd(1) - iEnd));
+ iEnd = m.capturedEnd(1);
+ m = matchHelper(eolRe, v, iEnd);
+ }
+ if (iEnd < v.size())
+ write(v.mid(iEnd, v.size() - iEnd));
+ return *this;
+ }
+ QStringView toAdd = v.mid(0, i);
+ if (!toAdd.trimmed().isEmpty())
+ textAddCallback(tAdd);
+ m_counter += i;
+ m_currentLine.append(toAdd);
+ m_currentColumnNr +=
+ IndentInfo(toAdd, m_options.formatOptions.tabSize, m_currentColumnNr).column;
+ lineChanged();
+ } else {
+ if (!v.trimmed().isEmpty())
+ textAddCallback(tAdd);
+ m_counter += v.size();
+ m_currentLine.append(v);
+ m_currentColumnNr +=
+ IndentInfo(v, m_options.formatOptions.tabSize, m_currentColumnNr).column;
+ lineChanged();
+ }
+ if (!eol.isEmpty()
+ || (m_options.maxLineLength > 0 && m_currentColumnNr > m_options.maxLineLength)) {
+ reindentAndSplit(eol);
+ }
+ return *this;
+}
+
+void LineWriter::flush()
+{
+ if (m_currentLine.size() > 0)
+ commitLine(QString());
+}
+
+void LineWriter::eof(bool shouldEnsureNewline)
+{
+ if (shouldEnsureNewline)
+ ensureNewline();
+ reindentAndSplit(QString(), true);
+}
+
+SourceLocation LineWriter::committedLocation() const
+{
+ return SourceLocation(m_utf16Offset, 0, m_lineNr, m_lineUtf16Offset);
+}
+
+PendingSourceLocationId LineWriter::startSourceLocation(SourceLocation *toUpdate)
+{
+ PendingSourceLocation res;
+ res.id = ++m_lastSourceLocationId;
+ res.value = currentSourceLocation();
+ res.toUpdate = toUpdate;
+ m_pendingSourceLocations.insert(res.id, res);
+ return res.id;
+}
+
+PendingSourceLocationId LineWriter::startSourceLocation(std::function<void(SourceLocation)> updater)
+{
+ PendingSourceLocation res;
+ res.id = ++m_lastSourceLocationId;
+ res.value = currentSourceLocation();
+ res.updater = updater;
+ m_pendingSourceLocations.insert(res.id, res);
+ return res.id;
+}
+
+void LineWriter::endSourceLocation(PendingSourceLocationId slId)
+{
+ if (m_pendingSourceLocations.contains(slId)) {
+ auto &pLoc = m_pendingSourceLocations[slId];
+ if (!pLoc.open) {
+ qWarning() << "Trying to close already closed PendingSourceLocation" << int(slId);
+ }
+ pLoc.open = false;
+ pLoc.value.length = m_utf16Offset + m_currentLine.size() - pLoc.value.offset;
+ } else {
+ qWarning() << "Trying to close non existing PendingSourceLocation" << int(slId);
+ }
+}
+
+int LineWriter::addTextAddCallback(std::function<bool(LineWriter &, TextAddType)> callback)
+{
+ int nextId = ++m_lastCallbackId;
+ Q_ASSERT(nextId != 0);
+ if (callback)
+ m_textAddCallbacks.insert(nextId, callback);
+ return nextId;
+}
+
+int LineWriter::addNewlinesAutospacerCallback(int nLines)
+{
+ return addTextAddCallback([nLines](LineWriter &self, TextAddType t) {
+ if (t == TextAddType::Normal) {
+ quint32 c = self.counter();
+ QString spacesToPreserve;
+ bool spaceOnly = QStringView(self.m_currentLine).trimmed().isEmpty();
+ if (spaceOnly && !self.m_currentLine.isEmpty())
+ spacesToPreserve = self.m_currentLine;
+ self.ensureNewline(nLines, LineWriter::TextAddType::Extra);
+ if (self.counter() != c && !spacesToPreserve.isEmpty())
+ self.write(spacesToPreserve, TextAddType::Extra);
+ return false;
+ } else {
+ return true;
+ }
+ });
+}
+
+void LineWriter::setLineIndent(int indentAmount)
+{
+ int startNonSpace = 0;
+ while (startNonSpace < m_currentLine.size() && m_currentLine.at(startNonSpace).isSpace())
+ ++startNonSpace;
+ int oldColumn = column(startNonSpace);
+ if (indentAmount >= 0) {
+ QString indent;
+ if (m_options.formatOptions.useTabs) {
+ indent = QStringLiteral(u"\t").repeated(indentAmount / m_options.formatOptions.tabSize)
+ + QStringLiteral(u" ").repeated(indentAmount % m_options.formatOptions.tabSize);
+ } else {
+ indent = QStringLiteral(u" ").repeated(indentAmount);
+ }
+ if (indent != m_currentLine.mid(0, startNonSpace)) {
+ quint32 colChange = indentAmount - oldColumn;
+ m_currentColumnNr += colChange;
+ qint32 oChange = indent.size() - startNonSpace;
+ m_currentLine = indent + m_currentLine.mid(startNonSpace);
+ m_currentColumnNr = column(m_currentLine.size());
+ lineChanged();
+ changeAtOffset(m_utf16Offset, oChange, oChange, 0);
+ }
+ }
+}
+
+void LineWriter::handleTrailingSpace(LineWriterOptions::TrailingSpace trailingSpace)
+{
+ switch (trailingSpace) {
+ case LineWriterOptions::TrailingSpace::Preserve:
+ break;
+ case LineWriterOptions::TrailingSpace::Remove: {
+ int lastNonSpace = m_currentLine.size();
+ while (lastNonSpace > 0 && m_currentLine.at(lastNonSpace - 1).isSpace())
+ --lastNonSpace;
+ if (lastNonSpace != m_currentLine.size()) {
+ qint32 oChange = lastNonSpace - m_currentLine.size();
+ m_currentLine = m_currentLine.mid(0, lastNonSpace);
+ changeAtOffset(m_utf16Offset + lastNonSpace, oChange, oChange, 0);
+ m_currentColumnNr =
+ column(m_currentLine.size()); // to be extra accurate in the potential split
+ lineChanged();
+ }
+ } break;
+ }
+}
+
+void LineWriter::reindentAndSplit(const QString &eol, bool eof)
+{
+ // maybe write out
+ if (!eol.isEmpty() || eof) {
+ handleTrailingSpace(m_options.codeTrailingSpace);
+ commitLine(eol);
+ }
+}
+
+SourceLocation LineWriter::currentSourceLocation() const
+{
+ return SourceLocation(m_utf16Offset + m_currentLine.size(), 0, m_lineNr,
+ m_lineUtf16Offset + m_currentLine.size());
+}
+
+void LineWriter::changeAtOffset(quint32 offset, qint32 change, qint32 colChange, qint32 lineChange)
+{
+ auto iEnd = m_pendingSourceLocations.end();
+ auto i = m_pendingSourceLocations.begin();
+ while (i != iEnd) {
+ i.value().changeAtOffset(offset, change, colChange, lineChange);
+ ++i;
+ }
+}
+
+int LineWriter::column(int index)
+{
+ if (index > m_currentLine.size())
+ index = m_currentLine.size();
+ IndentInfo iInfo(QStringView(m_currentLine).mid(0, index), m_options.formatOptions.tabSize,
+ m_columnNr);
+ return iInfo.column;
+}
+
+void LineWriter::textAddCallback(LineWriter::TextAddType t)
+{
+ if (m_textAddCallbacks.isEmpty())
+ return;
+ int iNow = (--m_textAddCallbacks.end()).key() + 1;
+ while (true) {
+ auto it = m_textAddCallbacks.lowerBound(iNow);
+ if (it == m_textAddCallbacks.begin())
+ break;
+ --it;
+ iNow = it.key();
+ if (!it.value()(*this, t))
+ m_textAddCallbacks.erase(it);
+ }
+}
+
+void LineWriter::commitLine(const QString &eol, TextAddType tType, int untilChar)
+{
+ if (untilChar == -1)
+ untilChar = m_currentLine.size();
+ bool isSpaceOnly = QStringView(m_currentLine).mid(0, untilChar).trimmed().isEmpty();
+ bool isEmptyNewline = !eol.isEmpty() && isSpaceOnly;
+ quint32 endCommit = m_utf16Offset + untilChar;
+ // update position, lineNr,...
+ // write out
+ for (SinkF &sink : m_innerSinks)
+ sink(m_currentLine.mid(0, untilChar));
+ m_utf16Offset += untilChar;
+ if (!eol.isEmpty()) {
+ m_utf16Offset += eol.size();
+ for (SinkF &sink : m_innerSinks)
+ sink(eol);
+ ++m_lineNr;
+ int oldCol = column(untilChar);
+ m_columnNr = 0;
+ m_lineUtf16Offset = 0;
+ changeAtOffset(m_utf16Offset, 0, -oldCol, 1);
+ } else {
+ m_columnNr = column(untilChar);
+ m_lineUtf16Offset += untilChar;
+ }
+ if (untilChar == m_currentLine.size()) {
+ willCommit();
+ m_currentLine.clear();
+ } else {
+ QString nextLine = m_currentLine.mid(untilChar);
+ m_currentLine = m_currentLine.mid(0, untilChar);
+ lineChanged();
+ willCommit();
+ m_currentLine = nextLine;
+ }
+ lineChanged();
+ m_currentColumnNr = column(m_currentLine.size());
+ TextAddType notifyType = tType;
+ switch (tType) {
+ case TextAddType::Normal:
+ if (eol.isEmpty())
+ notifyType = TextAddType::PartialCommit;
+ else
+ notifyType = TextAddType::Newline;
+ break;
+ case TextAddType::Extra:
+ if (eol.isEmpty())
+ notifyType = TextAddType::NewlineExtra;
+ else
+ notifyType = TextAddType::PartialCommit;
+ break;
+ case TextAddType::Newline:
+ case TextAddType::NewlineSplit:
+ case TextAddType::NewlineExtra:
+ case TextAddType::PartialCommit:
+ case TextAddType::Eof:
+ break;
+ }
+ if (isEmptyNewline)
+ ++m_committedEmptyLines;
+ else if (!isSpaceOnly)
+ m_committedEmptyLines = 0;
+ // commit finished pending
+ auto iEnd = m_pendingSourceLocations.end();
+ auto i = m_pendingSourceLocations.begin();
+ while (i != iEnd) {
+ auto &pLoc = i.value();
+ if (!pLoc.open && pLoc.utf16End() <= endCommit) {
+ pLoc.commit();
+ i = m_pendingSourceLocations.erase(i);
+ } else {
+ ++i;
+ }
+ }
+ // notify
+ textAddCallback(notifyType);
+}
+
+} // namespace Dom
+} // namespace QQmlJS
+QT_END_NAMESPACE
+
+#include "moc_qqmldomlinewriter_p.cpp"
diff --git a/src/qmldom/qqmldomlinewriter_p.h b/src/qmldom/qqmldomlinewriter_p.h
new file mode 100644
index 0000000000..86da8d6f54
--- /dev/null
+++ b/src/qmldom/qqmldomlinewriter_p.h
@@ -0,0 +1,225 @@
+// Copyright (C) 2021 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+
+#ifndef QQMLDOMLINEWRITER_P
+#define QQMLDOMLINEWRITER_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 "qqmldomstringdumper_p.h"
+
+#include <QtQml/private/qqmljssourcelocation_p.h>
+#include <QtCore/QObject>
+#include <QtCore/QAtomicInt>
+#include <QtCore/QMap>
+#include <functional>
+
+QT_BEGIN_NAMESPACE
+namespace QQmlJS {
+namespace Dom {
+
+class IndentInfo
+{
+public:
+ QStringView string;
+ QStringView trailingString;
+ int nNewlines = 0;
+ int column = 0;
+
+ IndentInfo(QStringView line, int tabSize, int initialColumn = 0)
+ {
+ string = line;
+ int fixup = 0;
+ if (initialColumn < 0) // we do not want % of negative numbers
+ fixup = (-initialColumn + tabSize - 1) / tabSize * tabSize;
+ column = initialColumn + fixup;
+ const QChar tab = QLatin1Char('\t');
+ int iStart = 0;
+ int len = line.size();
+ for (int i = 0; i < len; i++) {
+ if (line[i] == tab)
+ column = ((column / tabSize) + 1) * tabSize;
+ else if (line[i] == QLatin1Char('\n')
+ || (line[i] == QLatin1Char('\r')
+ && (i + 1 == len || line[i + 1] != QLatin1Char('\n')))) {
+ iStart = i + 1;
+ ++nNewlines;
+ column = 0;
+ } else if (!line[i].isLowSurrogate())
+ column++;
+ }
+ column -= fixup;
+ trailingString = line.mid(iStart);
+ }
+};
+
+class QMLDOM_EXPORT FormatOptions
+{
+public:
+ int tabSize = 4;
+ int indentSize = 4;
+ bool useTabs = false;
+};
+
+class QMLDOM_EXPORT LineWriterOptions
+{
+ Q_GADGET
+public:
+ enum class LineEndings { Unix, Windows, OldMacOs };
+ Q_ENUM(LineEndings)
+ enum class TrailingSpace { Preserve, Remove };
+ Q_ENUM(TrailingSpace)
+ enum class Update { None = 0, Expressions = 0x1, Locations = 0x2, All = 0x3, Default = All };
+ Q_ENUM(Update)
+ Q_DECLARE_FLAGS(Updates, Update)
+ enum class AttributesSequence { Normalize, Preserve };
+ Q_ENUM(AttributesSequence)
+
+ int maxLineLength = -1;
+ int strongMaxLineExtra = 20;
+ int minContentLength = 10;
+#if defined (Q_OS_WIN)
+ LineEndings lineEndings = LineEndings::Windows;
+#else
+ LineEndings lineEndings = LineEndings::Unix;
+#endif
+ TrailingSpace codeTrailingSpace = TrailingSpace::Remove;
+ TrailingSpace commentTrailingSpace = TrailingSpace::Remove;
+ TrailingSpace stringTrailingSpace = TrailingSpace::Preserve;
+ FormatOptions formatOptions;
+ Updates updateOptions = Update::Default;
+ AttributesSequence attributesSequence = AttributesSequence::Normalize;
+ bool objectsSpacing = false;
+ bool functionsSpacing = false;
+};
+Q_DECLARE_OPERATORS_FOR_FLAGS(LineWriterOptions::Updates)
+
+using PendingSourceLocationId = int;
+using PendingSourceLocationIdAtomic = QAtomicInt;
+class LineWriter;
+
+class QMLDOM_EXPORT PendingSourceLocation
+{
+ Q_GADGET
+public:
+ quint32 utf16Start() const;
+ quint32 utf16End() const;
+ void changeAtOffset(quint32 offset, qint32 change, qint32 colChange, qint32 lineChange);
+ void commit();
+ PendingSourceLocationId id;
+ SourceLocation value;
+ SourceLocation *toUpdate = nullptr;
+ std::function<void(SourceLocation)> updater = nullptr;
+ bool open = true;
+};
+
+class QMLDOM_EXPORT LineWriter
+{
+ Q_GADGET
+public:
+ enum class TextAddType {
+ Normal,
+ Extra,
+ Newline,
+ NewlineSplit,
+ NewlineExtra,
+ PartialCommit,
+ Eof
+ };
+
+ LineWriter(const SinkF &innerSink, const QString &fileName,
+ const LineWriterOptions &options = LineWriterOptions(), int lineNr = 0,
+ int columnNr = 0, int utf16Offset = 0, const QString &currentLine = QString());
+ std::function<void(QStringView)> sink()
+ {
+ return [this](QStringView s) { this->write(s); };
+ }
+
+ virtual ~LineWriter() { }
+
+ QList<SinkF> innerSinks() { return m_innerSinks; }
+ void addInnerSink(const SinkF &s) { m_innerSinks.append(s); }
+ LineWriter &ensureNewline(int nNewlines = 1, TextAddType t = TextAddType::Extra);
+ LineWriter &ensureSpace(TextAddType t = TextAddType::Extra);
+ LineWriter &ensureSpace(QStringView space, TextAddType t = TextAddType::Extra);
+
+ LineWriter &newline()
+ {
+ write(u"\n");
+ return *this;
+ }
+ LineWriter &space()
+ {
+ write(u" ");
+ return *this;
+ }
+ LineWriter &write(QStringView v, TextAddType tType = TextAddType::Normal);
+ LineWriter &write(QStringView v, SourceLocation *toUpdate)
+ {
+ auto pLoc = startSourceLocation(toUpdate);
+ write(v);
+ endSourceLocation(pLoc);
+ return *this;
+ }
+ void commitLine(const QString &eol, TextAddType t = TextAddType::Normal, int untilChar = -1);
+ void flush();
+ void eof(bool ensureNewline = true);
+ SourceLocation committedLocation() const;
+ PendingSourceLocationId startSourceLocation(SourceLocation *);
+ PendingSourceLocationId startSourceLocation(std::function<void(SourceLocation)>);
+ void endSourceLocation(PendingSourceLocationId);
+ quint32 counter() const { return m_counter; }
+ int addTextAddCallback(std::function<bool(LineWriter &, TextAddType)> callback);
+ bool removeTextAddCallback(int i) { return m_textAddCallbacks.remove(i); }
+ int addNewlinesAutospacerCallback(int nLines);
+ void handleTrailingSpace(LineWriterOptions::TrailingSpace s);
+ void setLineIndent(int indentAmount);
+ QString fileName() const { return m_fileName; }
+ const QString &currentLine() const { return m_currentLine; }
+ const LineWriterOptions &options() const { return m_options; }
+ virtual void lineChanged() { }
+ virtual void reindentAndSplit(const QString &eol, bool eof = false);
+ virtual void willCommit() { }
+
+private:
+ Q_DISABLE_COPY_MOVE(LineWriter)
+protected:
+ void changeAtOffset(quint32 offset, qint32 change, qint32 colChange, qint32 lineChange);
+ QString eolToWrite() const;
+ SourceLocation currentSourceLocation() const;
+ int column(int localIndex);
+ void textAddCallback(TextAddType t);
+
+ QList<SinkF> m_innerSinks;
+ QString m_fileName;
+ int m_lineNr = 0;
+ int m_columnNr = 0; // columnNr (starts at 0) of committed data
+ int m_lineUtf16Offset = 0; // utf16 offset since last newline (what is typically stores as
+ // SourceLocation::startColumn
+ int m_currentColumnNr = 0; // current columnNr (starts at 0)
+ int m_utf16Offset = 0; // utf16 offset since start for committed data
+ QString m_currentLine;
+ LineWriterOptions m_options;
+ PendingSourceLocationIdAtomic m_lastSourceLocationId;
+ QMap<PendingSourceLocationId, PendingSourceLocation> m_pendingSourceLocations;
+ QAtomicInt m_lastCallbackId;
+ QMap<int, std::function<bool(LineWriter &, TextAddType)>> m_textAddCallbacks;
+ quint32 m_counter = 0;
+ quint32 m_committedEmptyLines = 0x7FFFFFFF;
+ bool m_reindent = true;
+};
+
+} // namespace Dom
+} // namespace QQmlJS
+QT_END_NAMESPACE
+#endif
diff --git a/src/qmldom/qqmldommock.cpp b/src/qmldom/qqmldommock.cpp
new file mode 100644
index 0000000000..df49b9baaf
--- /dev/null
+++ b/src/qmldom/qqmldommock.cpp
@@ -0,0 +1,151 @@
+// Copyright (C) 2021 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+
+
+#include "qqmldommock_p.h"
+#include "qqmldomitem_p.h"
+#include "qqmldomcomments_p.h"
+
+#include <QtCore/QBasicMutex>
+#include <QtCore/QMutexLocker>
+
+QT_BEGIN_NAMESPACE
+
+namespace QQmlJS {
+namespace Dom {
+
+MockObject MockObject::copy() const
+{
+ QMap<QString, MockObject> newObjs;
+ auto objs = subObjects;
+ auto itO = objs.cbegin();
+ auto endO = objs.cend();
+ while (itO != endO) {
+ newObjs.insert(itO.key(), itO->copy());
+ ++itO;
+ }
+ return MockObject(pathFromOwner(), newObjs, subValues);
+}
+
+std::pair<QString, MockObject> MockObject::asStringPair() const
+{
+ return std::make_pair(pathFromOwner().last().headName(), *this);
+}
+
+bool MockObject::iterateDirectSubpaths(const DomItem &self, DirectVisitor visitor) const
+{
+ static QHash<QString, QString> knownFields;
+ static QBasicMutex m;
+ auto toField = [](const QString &f) -> QStringView {
+ QMutexLocker l(&m);
+ if (!knownFields.contains(f))
+ knownFields[f] = f;
+ return knownFields[f];
+ };
+ bool cont = CommentableDomElement::iterateDirectSubpaths(self, visitor);
+ auto itV = subValues.begin();
+ auto endV = subValues.end();
+ while (itV != endV) {
+ cont = cont && self.dvValue(visitor, PathEls::Field(toField(itV.key())), *itV);
+ ++itV;
+ }
+ auto itO = subObjects.begin();
+ auto endO = subObjects.end();
+ while (itO != endO) {
+ cont = cont && self.dvItem(visitor, PathEls::Field(toField(itO.key())), [&self, &itO]() {
+ return self.copy(&(*itO));
+ });
+ ++itO;
+ }
+ return cont;
+}
+
+std::shared_ptr<OwningItem> MockOwner::doCopy(const DomItem &) const
+{
+ return std::make_shared<MockOwner>(*this);
+}
+
+MockOwner::MockOwner(const MockOwner &o)
+ : OwningItem(o), pathFromTop(o.pathFromTop), subValues(o.subValues)
+{
+ auto objs = o.subObjects;
+ auto itO = objs.cbegin();
+ auto endO = objs.cend();
+ while (itO != endO) {
+ subObjects.insert(itO.key(), itO->copy());
+ ++itO;
+ }
+}
+
+std::shared_ptr<MockOwner> MockOwner::makeCopy(const DomItem &self) const
+{
+ return std::static_pointer_cast<MockOwner>(doCopy(self));
+}
+
+Path MockOwner::canonicalPath(const DomItem &) const
+{
+ return pathFromTop;
+}
+
+bool MockOwner::iterateDirectSubpaths(const DomItem &self, DirectVisitor visitor) const
+{
+ static QHash<QString, QString> knownFields;
+ static QBasicMutex m;
+ auto toField = [](const QString &f) -> QStringView {
+ QMutexLocker l(&m);
+ if (!knownFields.contains(f))
+ knownFields[f] = f;
+ return knownFields[f];
+ };
+ {
+ auto itV = subValues.begin();
+ auto endV = subValues.end();
+ while (itV != endV) {
+ if (!self.dvValue(visitor, PathEls::Field(toField(itV.key())), *itV))
+ return false;
+ ++itV;
+ }
+ }
+ {
+ auto itO = subObjects.begin();
+ auto endO = subObjects.end();
+ while (itO != endO) {
+ if (!self.dvItem(visitor, PathEls::Field(toField(itO.key())),
+ [&self, &itO]() { return self.copy(&(*itO)); }))
+ return false;
+ ++itO;
+ }
+ }
+ {
+ auto it = subMaps.begin();
+ auto end = subMaps.end();
+ while (it != end) {
+ if (!self.dvWrapField(visitor, toField(it.key()), it.value()))
+ return false;
+ ++it;
+ }
+ }
+ {
+ auto it = subMultiMaps.begin();
+ auto end = subMultiMaps.end();
+ while (it != end) {
+ if (!self.dvWrapField(visitor, toField(it.key()), it.value()))
+ return false;
+ ++it;
+ }
+ }
+ {
+ auto it = subLists.begin();
+ auto end = subLists.end();
+ while (it != end) {
+ if (!self.dvWrapField(visitor, toField(it.key()), it.value()))
+ return false;
+ ++it;
+ }
+ }
+ return true;
+}
+
+} // end namespace Dom
+} // end namespace QQmlJS
+QT_END_NAMESPACE
diff --git a/src/qmldom/qqmldommock_p.h b/src/qmldom/qqmldommock_p.h
new file mode 100644
index 0000000000..97504cc631
--- /dev/null
+++ b/src/qmldom/qqmldommock_p.h
@@ -0,0 +1,113 @@
+// Copyright (C) 2021 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+
+#ifndef QQMLDOMMOCK_P_H
+#define QQMLDOMMOCK_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 "qqmldomitem_p.h"
+#include "qqmldomconstants_p.h"
+#include "qqmldomelements_p.h"
+#include "qqmldomcomments_p.h"
+
+#include <QtQml/private/qqmljsast_p.h>
+#include <QtQml/private/qqmljsengine_p.h>
+
+#include <QtCore/QCborValue>
+#include <QtCore/QCborMap>
+#include <QtCore/QMutexLocker>
+#include <QtCore/QPair>
+
+#include <functional>
+#include <limits>
+
+QT_BEGIN_NAMESPACE
+
+namespace QQmlJS {
+namespace Dom {
+
+// mainly for debugging purposes
+class MockObject final : public CommentableDomElement
+{
+public:
+ constexpr static DomType kindValue = DomType::MockObject;
+ DomType kind() const override { return kindValue; }
+
+ MockObject(const Path &pathFromOwner = Path(), QMap<QString, MockObject> subObjects = {},
+ QMap<QString, QCborValue> subValues = {})
+ : CommentableDomElement(pathFromOwner), subObjects(subObjects), subValues(subValues)
+ {
+ }
+
+ MockObject copy() const;
+ std::pair<QString, MockObject> asStringPair() const;
+
+ bool iterateDirectSubpaths(const DomItem &self, DirectVisitor) const override;
+
+ QMap<QString, MockObject> subObjects;
+ QMap<QString, QCborValue> subValues;
+};
+
+// mainly for debugging purposes
+class MockOwner final : public OwningItem
+{
+protected:
+ std::shared_ptr<OwningItem> doCopy(const DomItem &self) const override;
+
+public:
+ constexpr static DomType kindValue = DomType::MockOwner;
+ DomType kind() const override { return kindValue; }
+
+ MockOwner(const Path &pathFromTop = Path(), int derivedFrom = 0,
+ QMap<QString, MockObject> subObjects = {}, QMap<QString, QCborValue> subValues = {},
+ QMap<QString, QMap<QString, MockObject>> subMaps = {},
+ QMap<QString, QMultiMap<QString, MockObject>> subMultiMaps = {},
+ QMap<QString, QList<MockObject>> subLists = {})
+ : OwningItem(derivedFrom),
+ pathFromTop(pathFromTop),
+ subObjects(subObjects),
+ subValues(subValues),
+ subMaps(subMaps),
+ subMultiMaps(subMultiMaps),
+ subLists(subLists)
+ {
+ }
+
+ MockOwner(const Path &pathFromTop, int derivedFrom, QDateTime dataRefreshedAt,
+ QMap<QString, MockObject> subObjects = {}, QMap<QString, QCborValue> subValues = {})
+ : OwningItem(derivedFrom, dataRefreshedAt),
+ pathFromTop(pathFromTop),
+ subObjects(subObjects),
+ subValues(subValues)
+ {
+ }
+
+ MockOwner(const MockOwner &o);
+
+ std::shared_ptr<MockOwner> makeCopy(const DomItem &self) const;
+ Path canonicalPath(const DomItem &self) const override;
+
+ bool iterateDirectSubpaths(const DomItem &self, DirectVisitor) const override;
+
+ Path pathFromTop;
+ QMap<QString, MockObject> subObjects;
+ QMap<QString, QCborValue> subValues;
+ QMap<QString, QMap<QString, MockObject>> subMaps;
+ QMap<QString, QMultiMap<QString, MockObject>> subMultiMaps;
+ QMap<QString, QList<MockObject>> subLists;
+};
+
+} // end namespace Dom
+} // end namespace QQmlJS
+QT_END_NAMESPACE
+#endif // QQMLDOMELEMENTS_P_H
diff --git a/src/qmldom/qqmldommoduleindex.cpp b/src/qmldom/qqmldommoduleindex.cpp
new file mode 100644
index 0000000000..d44c9ae003
--- /dev/null
+++ b/src/qmldom/qqmldommoduleindex.cpp
@@ -0,0 +1,407 @@
+// Copyright (C) 2020 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+#include "qqmldomtop_p.h"
+#include "qqmldomelements_p.h"
+#include "qqmldom_utils_p.h"
+
+#include <QtCore/QDir>
+#include <QtCore/QFileInfo>
+#include <QtCore/QScopeGuard>
+
+#include <memory>
+
+QT_BEGIN_NAMESPACE
+namespace QQmlJS {
+namespace Dom {
+
+static ErrorGroups myVersioningErrors()
+{
+ static ErrorGroups res = { { DomItem::domErrorGroup, NewErrorGroup("Exports"),
+ NewErrorGroup("Version") } };
+ return res;
+}
+
+static ErrorGroups myExportErrors()
+{
+ static ErrorGroups res = { { DomItem::domErrorGroup, NewErrorGroup("Exports") } };
+ return res;
+}
+
+bool ModuleScope::iterateDirectSubpaths(const DomItem &self, DirectVisitor visitor) const
+{
+ bool cont = true;
+ cont = cont && self.dvValueField(visitor, Fields::uri, uri);
+ cont = cont && self.dvWrapField(visitor, Fields::version, version);
+ cont = cont && self.dvItemField(visitor, Fields::exports, [this, &self]() {
+ int minorVersion = version.minorVersion;
+ return self.subMapItem(Map(
+ self.pathFromOwner().field(Fields::exports),
+ [minorVersion](const DomItem &mapExp, const QString &name) -> DomItem {
+ DomItem mapExpOw = mapExp.owner();
+ QList<DomItem> exports =
+ mapExp.ownerAs<ModuleIndex>()->exportsWithNameAndMinorVersion(
+ mapExpOw, name, minorVersion);
+ return mapExp.subListItem(List::fromQList<DomItem>(
+ mapExp.pathFromOwner().key(name), exports,
+ [](const DomItem &, const PathEls::PathComponent &, const DomItem &el) {
+ return el;
+ },
+ ListOptions::Normal));
+ },
+ [](const DomItem &mapExp) {
+ DomItem mapExpOw = mapExp.owner();
+ return mapExp.ownerAs<ModuleIndex>()->exportNames(mapExpOw);
+ },
+ QLatin1String("List<Exports>")));
+ });
+ cont = cont && self.dvItemField(visitor, Fields::symbols, [&self]() {
+ Path basePath = Path::Current(PathCurrent::Obj).field(Fields::exports);
+ return self.subMapItem(Map(
+ self.pathFromOwner().field(Fields::symbols),
+ [basePath](const DomItem &mapExp, const QString &name) -> DomItem {
+ QList<Path> symb({ basePath.key(name) });
+ return mapExp.subReferencesItem(PathEls::Key(name), symb);
+ },
+ [](const DomItem &mapExp) {
+ DomItem mapExpOw = mapExp.owner();
+ return mapExp.ownerAs<ModuleIndex>()->exportNames(mapExpOw);
+ },
+ QLatin1String("List<References>")));
+ });
+ cont = cont && self.dvItemField(visitor, Fields::autoExports, [this, &self]() {
+ return containingObject(self).field(Fields::autoExports);
+ });
+ return cont;
+}
+
+std::shared_ptr<OwningItem> ModuleIndex::doCopy(const DomItem &) const
+{
+ return std::make_shared<ModuleIndex>(*this);
+}
+
+ModuleIndex::ModuleIndex(const ModuleIndex &o)
+ : OwningItem(o), m_uri(o.uri()), m_majorVersion(o.majorVersion())
+{
+ QMap<int, ModuleScope *> scopes;
+ {
+ QMutexLocker l2(o.mutex());
+ m_qmltypesFilesPaths += o.m_qmltypesFilesPaths;
+ m_qmldirPaths += o.m_qmldirPaths;
+ m_directoryPaths += o.m_directoryPaths;
+ scopes = o.m_moduleScope;
+ }
+ auto it = scopes.begin();
+ auto end = scopes.end();
+ while (it != end) {
+ ensureMinorVersion((*it)->version.minorVersion);
+ ++it;
+ }
+}
+
+ModuleIndex::~ModuleIndex()
+{
+ QMap<int, ModuleScope *> scopes;
+ {
+ QMutexLocker l(mutex());
+ scopes = m_moduleScope;
+ m_moduleScope.clear();
+ }
+ auto it = scopes.begin();
+ auto end = scopes.end();
+ while (it != end) {
+ delete *it;
+ ++it;
+ }
+}
+
+bool ModuleIndex::iterateDirectSubpaths(const DomItem &self, DirectVisitor visitor) const
+{
+ bool cont = self.dvValueField(visitor, Fields::uri, uri());
+ cont = cont && self.dvValueField(visitor, Fields::majorVersion, majorVersion());
+ cont = cont && self.dvItemField(visitor, Fields::moduleScope, [this, &self]() {
+ return self.subMapItem(Map(
+ pathFromOwner(self).field(Fields::moduleScope),
+ [](const DomItem &map, const QString &minorVersionStr) {
+ bool ok;
+ int minorVersion = minorVersionStr.toInt(&ok);
+ if (minorVersionStr.isEmpty()
+ || minorVersionStr.compare(u"Latest", Qt::CaseInsensitive) == 0)
+ minorVersion = Version::Latest;
+ else if (!ok)
+ return DomItem();
+ return map.copy(map.ownerAs<ModuleIndex>()->ensureMinorVersion(minorVersion));
+ },
+ [this](const DomItem &) {
+ QSet<QString> res;
+ for (int el : minorVersions())
+ if (el >= 0)
+ res.insert(QString::number(el));
+ if (!minorVersions().isEmpty())
+ res.insert(QString());
+ return res;
+ },
+ QLatin1String("Map<List<Exports>>")));
+ });
+ cont = cont && self.dvItemField(visitor, Fields::sources, [this, &self]() {
+ return self.subReferencesItem(PathEls::Field(Fields::sources), sources());
+ });
+ cont = cont && self.dvValueLazyField(visitor, Fields::autoExports, [this, &self]() {
+ return autoExports(self);
+ });
+ return cont;
+}
+
+QSet<QString> ModuleIndex::exportNames(const DomItem &self) const
+{
+ QSet<QString> res;
+ QList<Path> mySources = sources();
+ for (int i = 0; i < mySources.size(); ++i) {
+ DomItem source = self.path(mySources.at(i));
+ res += source.field(Fields::exports).keys();
+ }
+ return res;
+}
+
+QList<DomItem> ModuleIndex::autoExports(const DomItem &self) const
+{
+ QList<DomItem> res;
+ Path selfPath = canonicalPath(self).field(Fields::autoExports);
+ RefCacheEntry cached = RefCacheEntry::forPath(self, selfPath);
+ QList<Path> cachedPaths;
+ switch (cached.cached) {
+ case RefCacheEntry::Cached::None:
+ case RefCacheEntry::Cached::First:
+ break;
+ case RefCacheEntry::Cached::All:
+ cachedPaths += cached.canonicalPaths;
+ if (cachedPaths.isEmpty())
+ return res;
+ }
+ DomItem env = self.environment();
+ if (!cachedPaths.isEmpty()) {
+ bool outdated = false;
+ for (const Path &p : cachedPaths) {
+ DomItem newEl = env.path(p);
+ if (!newEl) {
+ outdated = true;
+ qWarning() << "referenceCache outdated, reference at " << selfPath
+ << " leads to invalid path " << p;
+ break;
+ } else {
+ res.append(newEl);
+ }
+ }
+ if (outdated) {
+ res.clear();
+ } else {
+ return res;
+ }
+ }
+ QList<Path> mySources = sources();
+ QSet<QString> knownAutoImportUris;
+ QList<ModuleAutoExport> knownExports;
+ for (const Path &p : mySources) {
+ DomItem autoExports = self.path(p).field(Fields::autoExports);
+ for (const DomItem &i : autoExports.values()) {
+ if (const ModuleAutoExport *iPtr = i.as<ModuleAutoExport>()) {
+ if (!knownAutoImportUris.contains(iPtr->import.uri.toString())
+ || !knownExports.contains(*iPtr)) {
+ knownAutoImportUris.insert(iPtr->import.uri.toString());
+ knownExports.append(*iPtr);
+ res.append(i);
+ cachedPaths.append(i.canonicalPath());
+ }
+ }
+ }
+ }
+ RefCacheEntry::addForPath(self, selfPath,
+ RefCacheEntry { RefCacheEntry::Cached::All, cachedPaths });
+ return res;
+}
+
+QList<DomItem> ModuleIndex::exportsWithNameAndMinorVersion(const DomItem &self, const QString &name,
+ int minorVersion) const
+{
+ Path myPath = Paths::moduleScopePath(uri(), Version(majorVersion(), minorVersion))
+ .field(Fields::exports)
+ .key(name);
+ QList<Path> mySources = sources();
+ QList<DomItem> res;
+ QList<DomItem> undef;
+ if (minorVersion < 0)
+ minorVersion = std::numeric_limits<int>::max();
+ int vNow = Version::Undefined;
+ for (int i = 0; i < mySources.size(); ++i) {
+ DomItem source = self.path(mySources.at(i));
+ DomItem exports = source.field(Fields::exports).key(name);
+ int nExports = exports.indexes();
+ if (nExports == 0)
+ continue;
+ for (int j = 0; j < nExports; ++j) {
+ DomItem exportItem = exports.index(j);
+ if (!exportItem)
+ continue;
+ Version const *versionPtr = exportItem.field(Fields::version).as<Version>();
+ if (versionPtr == nullptr || !versionPtr->isValid()) {
+ undef.append(exportItem);
+ } else {
+ if (majorVersion() < 0)
+ self.addError(std::move(myVersioningErrors()
+ .error(tr("Module %1 (unversioned) has versioned entries "
+ "for '%2' from %3")
+ .arg(uri(), name,
+ source.canonicalPath().toString()))
+ .withPath(myPath)));
+ if ((versionPtr->majorVersion == majorVersion()
+ || versionPtr->majorVersion == Version::Undefined)
+ && versionPtr->minorVersion >= vNow
+ && versionPtr->minorVersion <= minorVersion) {
+ if (versionPtr->minorVersion > vNow)
+ res.clear();
+ res.append(exportItem);
+ vNow = versionPtr->minorVersion;
+ }
+ }
+ }
+ }
+ if (!undef.isEmpty()) {
+ if (!res.isEmpty()) {
+ self.addError(std::move(myVersioningErrors()
+ .error(tr("Module %1 (major version %2) has versioned and "
+ "unversioned entries for '%3'")
+ .arg(uri(), QString::number(majorVersion()), name))
+ .withPath(myPath)));
+ return res + undef;
+ } else {
+ return undef;
+ }
+ }
+ return res;
+}
+
+QList<Path> ModuleIndex::sources() const
+{
+ QList<Path> res;
+ QMutexLocker l(mutex());
+ res += m_qmltypesFilesPaths;
+ if (!m_qmldirPaths.isEmpty())
+ res += m_qmldirPaths.first();
+ else if (!m_directoryPaths.isEmpty())
+ res += m_directoryPaths.first();
+ return res;
+}
+
+ModuleScope *ModuleIndex::ensureMinorVersion(int minorVersion)
+{
+ if (minorVersion < 0)
+ minorVersion = Version::Latest;
+ {
+ QMutexLocker l(mutex());
+ auto it = m_moduleScope.constFind(minorVersion);
+ if (it != m_moduleScope.cend())
+ return *it;
+ }
+ ModuleScope *res = nullptr;
+ ModuleScope *newScope = new ModuleScope(m_uri, Version(majorVersion(), minorVersion));
+ auto cleanup = qScopeGuard([&newScope] { delete newScope; });
+ {
+ QMutexLocker l(mutex());
+ auto it = m_moduleScope.constFind(minorVersion);
+ if (it != m_moduleScope.cend()) {
+ res = *it;
+ } else {
+ res = newScope;
+ newScope = nullptr;
+ m_moduleScope.insert(minorVersion, res);
+ }
+ }
+ return res;
+}
+
+void ModuleIndex::mergeWith(const std::shared_ptr<ModuleIndex> &o)
+{
+ if (o) {
+ QList<Path> qmltypesPaths;
+ QMap<int, ModuleScope *> scopes;
+ {
+ QMutexLocker l2(o->mutex());
+ qmltypesPaths = o->m_qmltypesFilesPaths;
+ scopes = o->m_moduleScope;
+ }
+ {
+ QMutexLocker l(mutex());
+ for (const Path &qttPath : qmltypesPaths) {
+ if (!m_qmltypesFilesPaths.contains((qttPath)))
+ m_qmltypesFilesPaths.append(qttPath);
+ }
+ }
+ auto it = scopes.begin();
+ auto end = scopes.end();
+ while (it != end) {
+ ensureMinorVersion((*it)->version.minorVersion);
+ ++it;
+ }
+ }
+}
+
+QList<Path> ModuleIndex::qmldirsToLoad(const DomItem &self)
+{
+ // this always checks the filesystem to the qmldir file to load
+ DomItem env = self.environment();
+ std::shared_ptr<DomEnvironment> envPtr = env.ownerAs<DomEnvironment>();
+ QStringList subPathComponents = uri().split(u'.');
+ QString subPath = subPathComponents.join(u'/');
+ QString logicalPath;
+ QString subPathV = subPath + QChar::fromLatin1('.') + QString::number(majorVersion())
+ + QLatin1String("/qmldir");
+ QString dirPath;
+ if (majorVersion() >= 0) {
+ qCDebug(QQmlJSDomImporting)
+ << "ModuleIndex::qmldirsToLoad: Searching versioned module" << subPath
+ << majorVersion() << "in" << envPtr->loadPaths().join(u", ");
+ for (const QString &path : envPtr->loadPaths()) {
+ QDir dir(path);
+ QFileInfo fInfo(dir.filePath(subPathV));
+ if (fInfo.isFile()) {
+ qCDebug(QQmlJSDomImporting)
+ << "Found versioned module in " << fInfo.canonicalFilePath();
+ logicalPath = subPathV;
+ dirPath = fInfo.canonicalFilePath();
+ break;
+ }
+ }
+ }
+ if (dirPath.isEmpty()) {
+ qCDebug(QQmlJSDomImporting) << "ModuleIndex::qmldirsToLoad: Searching unversioned module"
+ << subPath << "in" << envPtr->loadPaths().join(u", ");
+ for (const QString &path : envPtr->loadPaths()) {
+ QDir dir(path);
+ QFileInfo fInfo(dir.filePath(subPath + QLatin1String("/qmldir")));
+ if (fInfo.isFile()) {
+ qCDebug(QQmlJSDomImporting)
+ << "Found unversioned module in " << fInfo.canonicalFilePath();
+ logicalPath = subPath + QLatin1String("/qmldir");
+ dirPath = fInfo.canonicalFilePath();
+ break;
+ }
+ }
+ }
+ if (!dirPath.isEmpty()) {
+ QMutexLocker l(mutex());
+ m_qmldirPaths = QList<Path>({ Paths::qmldirFilePath(dirPath) });
+ } else if (uri() != u"QML") {
+ const QString loadPaths = envPtr->loadPaths().join(u", "_s);
+ qCDebug(QQmlJSDomImporting) << "ModuleIndex::qmldirsToLoad: qmldir at"
+ << (uri() + u"/qmldir"_s) << " was not found in " << loadPaths;
+ addErrorLocal(
+ myExportErrors()
+ .warning(tr("Failed to find main qmldir file for %1 %2 in %3.")
+ .arg(uri(), QString::number(majorVersion()), loadPaths))
+ .handle());
+ }
+ return qmldirPaths();
+}
+
+} // end namespace Dom
+} // end namespace QQmlJS
+QT_END_NAMESPACE
diff --git a/src/qmldom/qqmldommoduleindex_p.h b/src/qmldom/qqmldommoduleindex_p.h
new file mode 100644
index 0000000000..f2b2731624
--- /dev/null
+++ b/src/qmldom/qqmldommoduleindex_p.h
@@ -0,0 +1,141 @@
+// Copyright (C) 2020 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+
+#ifndef QQMLDOMMODULEINDEX_P_H
+#define QQMLDOMMODULEINDEX_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 "qqmldomelements_p.h"
+
+QT_BEGIN_NAMESPACE
+
+namespace QQmlJS {
+namespace Dom {
+
+class QMLDOM_EXPORT ModuleScope final : public DomBase
+{
+public:
+ constexpr static DomType kindValue = DomType::ModuleScope;
+ DomType kind() const override { return kindValue; }
+
+ ModuleScope(const QString &uri = QString(), const Version &version = Version())
+ : uri(uri), version(version)
+ {
+ }
+
+ Path pathFromOwner() const
+ {
+ return Path::Field(Fields::moduleScope)
+ .key(version.isValid() ? QString::number(version.minorVersion) : QString());
+ }
+ Path pathFromOwner(const DomItem &) const override { return pathFromOwner(); }
+ Path canonicalPath(const DomItem &self) const override
+ {
+ return self.owner().canonicalPath().path(pathFromOwner());
+ }
+ bool iterateDirectSubpaths(const DomItem &self, DirectVisitor visitor) const override;
+
+ QString uri;
+ Version version;
+};
+
+class QMLDOM_EXPORT ModuleIndex final : public OwningItem
+{
+ Q_DECLARE_TR_FUNCTIONS(ModuleIndex);
+
+protected:
+ std::shared_ptr<OwningItem> doCopy(const DomItem &self) const override;
+
+public:
+ enum class Status { NotLoaded, Loading, Loaded };
+ constexpr static DomType kindValue = DomType::ModuleIndex;
+ DomType kind() const override { return kindValue; }
+
+ ModuleIndex(
+ const QString &uri, int majorVersion, int derivedFrom = 0,
+ const QDateTime &lastDataUpdateAt = QDateTime::fromMSecsSinceEpoch(0, QTimeZone::UTC))
+ : OwningItem(derivedFrom, lastDataUpdateAt), m_uri(uri), m_majorVersion(majorVersion)
+ {
+ }
+
+ ModuleIndex(const ModuleIndex &o);
+
+ ~ModuleIndex();
+
+ std::shared_ptr<ModuleIndex> makeCopy(const DomItem &self) const
+ {
+ return std::static_pointer_cast<ModuleIndex>(doCopy(self));
+ }
+
+ Path canonicalPath(const DomItem &) const override
+ {
+ return Paths::moduleIndexPath(uri(), majorVersion());
+ }
+
+ bool iterateDirectSubpaths(const DomItem &self, DirectVisitor visitor) const override;
+
+ QSet<QString> exportNames(const DomItem &self) const;
+
+ QList<DomItem> exportsWithNameAndMinorVersion(const DomItem &self, const QString &name,
+ int minorVersion) const;
+
+ QString uri() const { return m_uri; }
+ int majorVersion() const { return m_majorVersion; }
+ QList<Path> sources() const;
+
+ QList<int> minorVersions() const
+ {
+ QMutexLocker l(mutex());
+ return m_moduleScope.keys();
+ }
+ ModuleScope *ensureMinorVersion(int minorVersion);
+ void mergeWith(const std::shared_ptr<ModuleIndex> &o);
+ void addQmltypeFilePath(const Path &p)
+ {
+ QMutexLocker l(mutex());
+ if (!m_qmltypesFilesPaths.contains(p))
+ m_qmltypesFilesPaths.append(p);
+ }
+
+ QList<Path> qmldirsToLoad(const DomItem &self);
+ QList<Path> qmltypesFilesPaths() const
+ {
+ QMutexLocker l(mutex());
+ return m_qmltypesFilesPaths;
+ }
+ QList<Path> qmldirPaths() const
+ {
+ QMutexLocker l(mutex());
+ return m_qmldirPaths;
+ }
+ QList<Path> directoryPaths() const
+ {
+ QMutexLocker l(mutex());
+ return m_directoryPaths;
+ }
+ QList<DomItem> autoExports(const DomItem &self) const;
+
+private:
+ QString m_uri;
+ int m_majorVersion;
+
+ QList<Path> m_qmltypesFilesPaths;
+ QList<Path> m_qmldirPaths;
+ QList<Path> m_directoryPaths;
+ QMap<int, ModuleScope *> m_moduleScope;
+};
+
+} // end namespace Dom
+} // end namespace QQmlJS
+QT_END_NAMESPACE
+#endif // QQMLDOMMODULEINDEX_P_H
diff --git a/src/qmldom/qqmldomoutwriter.cpp b/src/qmldom/qqmldomoutwriter.cpp
new file mode 100644
index 0000000000..30b75e1155
--- /dev/null
+++ b/src/qmldom/qqmldomoutwriter.cpp
@@ -0,0 +1,368 @@
+// Copyright (C) 2020 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+
+#include "qqmldomoutwriter_p.h"
+#include "qqmldomattachedinfo_p.h"
+#include "qqmldomlinewriter_p.h"
+#include "qqmldomitem_p.h"
+#include "qqmldomcomments_p.h"
+#include "qqmldomexternalitems_p.h"
+#include "qqmldomtop_p.h"
+
+#include <QtCore/QLoggingCategory>
+
+QT_BEGIN_NAMESPACE
+namespace QQmlJS {
+namespace Dom {
+
+OutWriterState::OutWriterState(
+ const Path &itCanonicalPath, const DomItem &it, const FileLocations::Tree &fLoc)
+ : itemCanonicalPath(itCanonicalPath), item(it), currentMap(fLoc)
+{
+ DomItem cRegions = it.field(Fields::comments);
+ if (const RegionComments *cRegionsPtr = cRegions.as<RegionComments>())
+ pendingComments = cRegionsPtr->regionComments();
+}
+
+void OutWriterState::closeState(OutWriter &w)
+{
+ if (w.lineWriter.options().updateOptions & LineWriterOptions::Update::Locations)
+ w.lineWriter.endSourceLocation(fullRegionId);
+ if (!pendingRegions.isEmpty()) {
+ qCWarning(writeOutLog) << "PendingRegions non empty when closing item"
+ << pendingRegions.keys();
+ auto iend = pendingRegions.end();
+ auto it = pendingRegions.begin();
+ while (it == iend) {
+ w.lineWriter.endSourceLocation(it.value());
+ ++it;
+ }
+ }
+ if (!w.skipComments && !pendingComments.isEmpty())
+ qCWarning(writeOutLog) << "PendingComments when closing item "
+ << item.canonicalPath().toString() << "for regions"
+ << pendingComments.keys();
+}
+
+OutWriterState &OutWriter::state(int i)
+{
+ return states[states.size() - 1 - i];
+}
+
+void OutWriter::itemStart(const DomItem &it)
+{
+ if (!topLocation->path())
+ topLocation->setPath(it.canonicalPath());
+ bool updateLocs = lineWriter.options().updateOptions & LineWriterOptions::Update::Locations;
+ FileLocations::Tree newFLoc = topLocation;
+ Path itP = it.canonicalPath();
+ if (updateLocs) {
+ if (!states.isEmpty()
+ && states.last().itemCanonicalPath
+ == itP.mid(0, states.last().itemCanonicalPath.length())) {
+ int oldL = states.last().itemCanonicalPath.length();
+ newFLoc = FileLocations::ensure(states.last().currentMap,
+ itP.mid(oldL, itP.length() - oldL),
+ AttachedInfo::PathType::Relative);
+
+ } else {
+ newFLoc = FileLocations::ensure(topLocation, itP, AttachedInfo::PathType::Canonical);
+ }
+ }
+ states.append(OutWriterState(itP, it, newFLoc));
+ if (updateLocs)
+ state().fullRegionId = lineWriter.startSourceLocation(
+ [newFLoc](SourceLocation l) { FileLocations::updateFullLocation(newFLoc, l); });
+ regionStart(MainRegion);
+}
+
+void OutWriter::itemEnd(const DomItem &it)
+{
+ Q_ASSERT(states.size() > 0);
+ Q_ASSERT(state().item == it);
+ regionEnd(MainRegion);
+ state().closeState(*this);
+ states.removeLast();
+}
+
+void OutWriter::regionStart(FileLocationRegion region)
+{
+ Q_ASSERT(!state().pendingRegions.contains(region));
+ FileLocations::Tree fMap = state().currentMap;
+ if (!skipComments && state().pendingComments.contains(region)) {
+ bool updateLocs = lineWriter.options().updateOptions & LineWriterOptions::Update::Locations;
+ QList<SourceLocation> *cLocs =
+ (updateLocs ? &(fMap->info().preCommentLocations[region]) : nullptr);
+ state().pendingComments[region].writePre(*this, cLocs);
+ }
+ state().pendingRegions[region] = lineWriter.startSourceLocation(
+ [region, fMap](SourceLocation l) { FileLocations::addRegion(fMap, region, l); });
+}
+
+void OutWriter::regionEnd(FileLocationRegion region)
+{
+ Q_ASSERT(state().pendingRegions.contains(region));
+ FileLocations::Tree fMap = state().currentMap;
+ lineWriter.endSourceLocation(state().pendingRegions.value(region));
+ state().pendingRegions.remove(region);
+ if (state().pendingComments.contains(region)) {
+ if (!skipComments) {
+ bool updateLocs =
+ lineWriter.options().updateOptions & LineWriterOptions::Update::Locations;
+ QList<SourceLocation> *cLocs =
+ (updateLocs ? &(fMap->info().postCommentLocations[region]) : nullptr);
+ state().pendingComments[region].writePost(*this, cLocs);
+ }
+ state().pendingComments.remove(region);
+ }
+}
+
+/*!
+\internal
+Helper method for writeRegion(FileLocationRegion region) that allows to use
+\c{writeRegion(ColonTokenRegion);} instead of having to write out the more error-prone
+\c{writeRegion(ColonTokenRegion, ":");} for tokens and keywords.
+*/
+OutWriter &OutWriter::writeRegion(FileLocationRegion region)
+{
+ QString codeForRegion;
+ switch (region) {
+ case ComponentKeywordRegion:
+ codeForRegion = u"component"_s;
+ break;
+ case IdColonTokenRegion:
+ case ColonTokenRegion:
+ codeForRegion = u":"_s;
+ break;
+ case ImportTokenRegion:
+ codeForRegion = u"import"_s;
+ break;
+ case AsTokenRegion:
+ codeForRegion = u"as"_s;
+ break;
+ case OnTokenRegion:
+ codeForRegion = u"on"_s;
+ break;
+ case IdTokenRegion:
+ codeForRegion = u"id"_s;
+ break;
+ case LeftBraceRegion:
+ codeForRegion = u"{"_s;
+ break;
+ case RightBraceRegion:
+ codeForRegion = u"}"_s;
+ break;
+ case LeftBracketRegion:
+ codeForRegion = u"["_s;
+ break;
+ case RightBracketRegion:
+ codeForRegion = u"]"_s;
+ break;
+ case LeftParenthesisRegion:
+ codeForRegion = u"("_s;
+ break;
+ case RightParenthesisRegion:
+ codeForRegion = u")"_s;
+ break;
+ case EnumKeywordRegion:
+ codeForRegion = u"enum"_s;
+ break;
+ case DefaultKeywordRegion:
+ codeForRegion = u"default"_s;
+ break;
+ case RequiredKeywordRegion:
+ codeForRegion = u"required"_s;
+ break;
+ case ReadonlyKeywordRegion:
+ codeForRegion = u"readonly"_s;
+ break;
+ case PropertyKeywordRegion:
+ codeForRegion = u"property"_s;
+ break;
+ case FunctionKeywordRegion:
+ codeForRegion = u"function"_s;
+ break;
+ case SignalKeywordRegion:
+ codeForRegion = u"signal"_s;
+ break;
+ case ReturnKeywordRegion:
+ codeForRegion = u"return"_s;
+ break;
+ case EllipsisTokenRegion:
+ codeForRegion = u"..."_s;
+ break;
+ case EqualTokenRegion:
+ codeForRegion = u"="_s;
+ break;
+ case PragmaKeywordRegion:
+ codeForRegion = u"pragma"_s;
+ break;
+ case CommaTokenRegion:
+ codeForRegion = u","_s;
+ break;
+ case ForKeywordRegion:
+ codeForRegion = u"for"_s;
+ break;
+ case ElseKeywordRegion:
+ codeForRegion = u"else"_s;
+ break;
+ case DoKeywordRegion:
+ codeForRegion = u"do"_s;
+ break;
+ case WhileKeywordRegion:
+ codeForRegion = u"while"_s;
+ break;
+ case TryKeywordRegion:
+ codeForRegion = u"try"_s;
+ break;
+ case CatchKeywordRegion:
+ codeForRegion = u"catch"_s;
+ break;
+ case FinallyKeywordRegion:
+ codeForRegion = u"finally"_s;
+ break;
+ case CaseKeywordRegion:
+ codeForRegion = u"case"_s;
+ break;
+ case ThrowKeywordRegion:
+ codeForRegion = u"throw"_s;
+ break;
+ case ContinueKeywordRegion:
+ codeForRegion = u"continue"_s;
+ break;
+ case BreakKeywordRegion:
+ codeForRegion = u"break"_s;
+ break;
+ case QuestionMarkTokenRegion:
+ codeForRegion = u"?"_s;
+ break;
+ case SemicolonTokenRegion:
+ codeForRegion = u";"_s;
+ break;
+
+ // not keywords:
+ case ImportUriRegion:
+ case IdNameRegion:
+ case IdentifierRegion:
+ case PragmaValuesRegion:
+ case MainRegion:
+ case OnTargetRegion:
+ case TypeIdentifierRegion:
+ case FirstSemicolonTokenRegion:
+ case SecondSemicolonRegion:
+ case InOfTokenRegion:
+ case OperatorTokenRegion:
+ case VersionRegion:
+ case EnumValueRegion:
+ Q_ASSERT_X(false, "regionToString", "Using regionToString on a value or an identifier!");
+ return *this;
+ }
+
+ return writeRegion(region, codeForRegion);
+}
+
+OutWriter &OutWriter::writeRegion(FileLocationRegion region, QStringView toWrite)
+{
+ regionStart(region);
+ lineWriter.write(toWrite);
+ regionEnd(region);
+ return *this;
+}
+/*!
+ \internal
+ Restores written out FileItem using intermediate information saved during DOM traversal.
+ It enables verifying DOM consistency of the written item later.
+
+ At the moment of writing, intermediate information consisting only of UpdatedScriptExpression,
+ however this is subject for change. The process of restoration is the following:
+ 1. Creating copy of the initial fileItem
+ 2. Updating relevant data/subitems modified during the WriteOut
+ 3. Returning an item containing updates.
+ */
+DomItem OutWriter::restoreWrittenFileItem(const DomItem &fileItem)
+{
+ switch (fileItem.internalKind()) {
+ case DomType::QmlFile:
+ return writtenQmlFileItem(fileItem, fileItem.canonicalPath());
+ case DomType::JsFile:
+ return writtenJsFileItem(fileItem, fileItem.canonicalPath());
+ default:
+ qCWarning(writeOutLog) << fileItem.internalKind() << " is not supported";
+ return DomItem{};
+ }
+}
+
+DomItem OutWriter::writtenQmlFileItem(const DomItem &fileItem, const Path &filePath)
+{
+ Q_ASSERT(fileItem.internalKind() == DomType::QmlFile);
+ auto mutableFile = fileItem.makeCopy(DomItem::CopyOption::EnvDisconnected);
+ // QmlFile specific visitor for reformattedScriptExpressions tree
+ // lambda function responsible for the update of the initial expression by the formatted one
+ auto exprUpdater = [&mutableFile, filePath](
+ const Path &p, const UpdatedScriptExpression::Tree &t) {
+ if (std::shared_ptr<ScriptExpression> formattedExpr = t->info().expr) {
+ Q_ASSERT(p.mid(0, filePath.length()) == filePath);
+ MutableDomItem originalExprItem = mutableFile.path(p.mid(filePath.length()));
+ if (!originalExprItem)
+ qCWarning(writeOutLog) << "failed to get" << p.mid(filePath.length()) << "from"
+ << mutableFile.canonicalPath();
+ // Verifying originalExprItem.as<ScriptExpression>() == false is handy
+ // because we can't call setScript on the ScriptExpression itself and it needs to
+ // be called on the container / parent item. See setScript for details
+ else if (formattedExpr->ast()
+ || (!originalExprItem.as<ScriptExpression>()
+ || !originalExprItem.as<ScriptExpression>()->ast()))
+ originalExprItem.setScript(formattedExpr);
+ else {
+ logScriptExprUpdateSkipped(originalExprItem.item(),
+ originalExprItem.canonicalPath(), formattedExpr);
+ }
+ }
+ return true;
+ };
+ // update relevant formatted expressions
+ UpdatedScriptExpression::visitTree(reformattedScriptExpressions, exprUpdater);
+ return mutableFile.item();
+}
+
+DomItem OutWriter::writtenJsFileItem(const DomItem &fileItem, const Path &filePath)
+{
+ Q_ASSERT(fileItem.internalKind() == DomType::JsFile);
+ auto mutableFile = fileItem.makeCopy(DomItem::CopyOption::EnvDisconnected);
+ UpdatedScriptExpression::visitTree(
+ reformattedScriptExpressions,
+ [&mutableFile, filePath](const Path &p, const UpdatedScriptExpression::Tree &t) {
+ if (std::shared_ptr<ScriptExpression> formattedExpr = t->info().expr) {
+ Q_ASSERT(p.mid(0, filePath.length()) == filePath);
+ mutableFile.mutableAs<JsFile>()->setExpression(formattedExpr);
+ }
+ return true;
+ });
+ return mutableFile.item();
+}
+
+void OutWriter::logScriptExprUpdateSkipped(
+ const DomItem &exprItem, const Path &exprPath,
+ const std::shared_ptr<ScriptExpression> &formattedExpr)
+{
+ qCWarning(writeOutLog).noquote() << "Skipped update of reformatted ScriptExpression with "
+ "code:\n---------------\n"
+ << formattedExpr->code() << "\n---------------\n preCode:" <<
+ [&formattedExpr](Sink s) { sinkEscaped(s, formattedExpr->preCode()); }
+ << "\n postCode: " <<
+ [&formattedExpr](Sink s) { sinkEscaped(s, formattedExpr->postCode()); }
+ << "\n as it failed standalone reparse with errors:" <<
+ [&exprItem, &exprPath, &formattedExpr](Sink s) {
+ exprItem.copy(formattedExpr, exprPath)
+ .iterateErrors(
+ [s](const DomItem &, const ErrorMessage &msg) {
+ s(u"\n ");
+ msg.dump(s);
+ return true;
+ },
+ true);
+ } << "\n";
+}
+} // namespace Dom
+} // namespace QQmlJS
+QT_END_NAMESPACE
diff --git a/src/qmldom/qqmldomoutwriter_p.h b/src/qmldom/qqmldomoutwriter_p.h
new file mode 100644
index 0000000000..8b00223ea2
--- /dev/null
+++ b/src/qmldom/qqmldomoutwriter_p.h
@@ -0,0 +1,164 @@
+// Copyright (C) 2020 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+
+#ifndef QMLDOMOUTWRITER_P_H
+#define QMLDOMOUTWRITER_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 "qqmldom_fwd_p.h"
+#include "qqmldomattachedinfo_p.h"
+#include "qqmldomlinewriter_p.h"
+
+#include <QtCore/QLoggingCategory>
+
+QT_BEGIN_NAMESPACE
+
+namespace QQmlJS {
+namespace Dom {
+
+class QMLDOM_EXPORT OutWriterState
+{
+public:
+ OutWriterState(const Path &itPath, const DomItem &it, const FileLocations::Tree &fLoc);
+
+ void closeState(OutWriter &);
+
+ Path itemCanonicalPath;
+ DomItem item;
+ PendingSourceLocationId fullRegionId;
+ FileLocations::Tree currentMap;
+ QMap<FileLocationRegion, PendingSourceLocationId> pendingRegions;
+ QMap<FileLocationRegion, CommentedElement> pendingComments;
+};
+
+class QMLDOM_EXPORT OutWriter
+{
+public:
+ int indent = 0;
+ int indenterId = -1;
+ bool indentNextlines = false;
+ bool skipComments = false;
+ LineWriter &lineWriter;
+ Path currentPath;
+ FileLocations::Tree topLocation;
+ QString writtenStr;
+ UpdatedScriptExpression::Tree reformattedScriptExpressions;
+ QList<OutWriterState> states;
+
+ explicit OutWriter(LineWriter &lw)
+ : lineWriter(lw),
+ topLocation(FileLocations::createTree(Path())),
+ reformattedScriptExpressions(UpdatedScriptExpression::createTree(Path()))
+ {
+ lineWriter.addInnerSink([this](QStringView s) { writtenStr.append(s); });
+ indenterId =
+ lineWriter.addTextAddCallback([this](LineWriter &, LineWriter::TextAddType tt) {
+ if (indentNextlines && tt == LineWriter::TextAddType::Normal
+ && QStringView(lineWriter.currentLine()).trimmed().isEmpty())
+ lineWriter.setLineIndent(indent);
+ return true;
+ });
+ }
+
+ OutWriterState &state(int i = 0);
+
+ int increaseIndent(int level = 1)
+ {
+ int oldIndent = indent;
+ indent += lineWriter.options().formatOptions.indentSize * level;
+ return oldIndent;
+ }
+ int decreaseIndent(int level = 1, int expectedIndent = -1)
+ {
+ indent -= lineWriter.options().formatOptions.indentSize * level;
+ Q_ASSERT(expectedIndent < 0 || expectedIndent == indent);
+ return indent;
+ }
+
+ void itemStart(const DomItem &it);
+ void itemEnd(const DomItem &it);
+ void regionStart(FileLocationRegion region);
+ void regionEnd(FileLocationRegion regino);
+
+ quint32 counter() const { return lineWriter.counter(); }
+ OutWriter &writeRegion(FileLocationRegion region, QStringView toWrite);
+ OutWriter &writeRegion(FileLocationRegion region);
+ OutWriter &ensureNewline(int nNewlines = 1)
+ {
+ lineWriter.ensureNewline(nNewlines);
+ return *this;
+ }
+ OutWriter &ensureSpace()
+ {
+ lineWriter.ensureSpace();
+ return *this;
+ }
+ OutWriter &ensureSpace(QStringView space)
+ {
+ lineWriter.ensureSpace(space);
+ return *this;
+ }
+ OutWriter &newline()
+ {
+ lineWriter.newline();
+ return *this;
+ }
+ OutWriter &space()
+ {
+ lineWriter.space();
+ return *this;
+ }
+ OutWriter &write(QStringView v, LineWriter::TextAddType t = LineWriter::TextAddType::Normal)
+ {
+ lineWriter.write(v, t);
+ return *this;
+ }
+ OutWriter &write(QStringView v, SourceLocation *toUpdate)
+ {
+ lineWriter.write(v, toUpdate);
+ return *this;
+ }
+ void flush() { lineWriter.flush(); }
+ void eof(bool ensureNewline = true) { lineWriter.eof(ensureNewline); }
+ int addNewlinesAutospacerCallback(int nLines)
+ {
+ return lineWriter.addNewlinesAutospacerCallback(nLines);
+ }
+ int addTextAddCallback(std::function<bool(LineWriter &, LineWriter::TextAddType)> callback)
+ {
+ return lineWriter.addTextAddCallback(callback);
+ }
+ bool removeTextAddCallback(int i) { return lineWriter.removeTextAddCallback(i); }
+ void addReformattedScriptExpression(const Path &p, const std::shared_ptr<ScriptExpression> &exp)
+ {
+ if (auto updExp = UpdatedScriptExpression::ensure(reformattedScriptExpressions, p,
+ AttachedInfo::PathType::Canonical)) {
+ updExp->info().expr = exp;
+ }
+ }
+ DomItem restoreWrittenFileItem(const DomItem &fileItem);
+
+private:
+ DomItem writtenQmlFileItem(const DomItem &fileItem, const Path &filePath);
+ DomItem writtenJsFileItem(const DomItem &fileItem, const Path &filePath);
+ static void logScriptExprUpdateSkipped(
+ const DomItem &exprItem, const Path &exprPath,
+ const std::shared_ptr<ScriptExpression> &formattedExpr);
+};
+
+} // end namespace Dom
+} // end namespace QQmlJS
+
+QT_END_NAMESPACE
+#endif // QMLDOMOUTWRITER_P_H
diff --git a/src/qmldom/qqmldompath.cpp b/src/qmldom/qqmldompath.cpp
index 60086ced10..d2b4582bc0 100644
--- a/src/qmldom/qqmldompath.cpp
+++ b/src/qmldom/qqmldompath.cpp
@@ -1,41 +1,5 @@
-/****************************************************************************
-**
-** 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 "qqmldompath_p.h"
+// Copyright (C) 2020 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
#include "qqmldomitem_p.h"
#include "qqmldomerrormessage_p.h"
#include <QtCore/QDebug>
@@ -101,8 +65,6 @@ The current contexts are:
\li \l{@module} The current module instantiation.
\li \l{@ids} The ids in the current component.
\li \l{@types} All the types in the current component (reachable through imports, respecting renames)
-\li \l{@instantiation} The current instantiation, either component instantiation or module
- instantiation
\li \l{@lookupStrict} The strict lookup inside the current object: localJS, ids, properties, proto
properties, component, its properties, global context, oterwise error
\li \l{@lookupDynamic} The default lookup inside the current object: localJS, ids, properties, proto
@@ -112,15 +74,16 @@ The current contexts are:
\endlist
*/
-void Base::dump(Sink sink) const {
- if (hasSquareBrackets())
+void Base::dump(const Sink &sink, const QString &name, bool hasSquareBrackets) const {
+ if (hasSquareBrackets)
sink(u"[");
- sink(name());
- if (hasSquareBrackets())
+ sink(name);
+ if (hasSquareBrackets)
sink(u"]");
}
-Filter::Filter(function<bool(DomItem)> f, QStringView filterDescription): filterFunction(f), filterDescription(filterDescription) {}
+Filter::Filter(const function<bool(const DomItem &)> &f, QStringView filterDescription)
+ : filterFunction(f), filterDescription(filterDescription) {}
QString Filter::name() const {
return QLatin1String("?(%1)").arg(filterDescription); }
@@ -128,7 +91,7 @@ QString Filter::name() const {
bool Filter::checkName(QStringView s) const
{
return s.startsWith(u"?(")
- && s.mid(2, s.length()-3) == filterDescription
+ && s.mid(2, s.size()-3) == filterDescription
&& s.endsWith(u")");
}
@@ -138,13 +101,6 @@ enum class ParserState{
End
};
-} // namespace PathEls
-
-using namespace PathEls;
-
-PathComponent::~PathComponent(){
-}
-
int PathComponent::cmp(const PathComponent &p1, const PathComponent &p2)
{
int k1 = static_cast<int>(p1.kind());
@@ -157,37 +113,46 @@ int PathComponent::cmp(const PathComponent &p1, const PathComponent &p2)
case Kind::Empty:
return 0;
case Kind::Field:
- return p1.data.field.fieldName.compare(p2.data.field.fieldName);
+ return std::get<Field>(p1.m_data).fieldName.compare(std::get<Field>(p2.m_data).fieldName);
case Kind::Index:
- if (p1.data.index.indexValue < p2.data.index.indexValue)
+ if (std::get<Index>(p1.m_data).indexValue < std::get<Index>(p2.m_data).indexValue)
return -1;
- if (p1.data.index.indexValue > p2.data.index.indexValue)
+ if (std::get<Index>(p1.m_data).indexValue > std::get<Index>(p2.m_data).indexValue)
return 1;
return 0;
case Kind::Key:
- return p1.data.key.keyValue.compare(p2.data.key.keyValue);
+ return std::get<Key>(p1.m_data).keyValue.compare(std::get<Key>(p2.m_data).keyValue);
case Kind::Root:
{
- int c = int(p1.data.root.contextKind) - int(p2.data.root.contextKind);
+ PathRoot k1 = std::get<Root>(p1.m_data).contextKind;
+ PathRoot k2 = std::get<Root>(p2.m_data).contextKind;
+ if (k1 == PathRoot::Env || k1 == PathRoot::Universe)
+ k1 = PathRoot::Top;
+ if (k2 == PathRoot::Env || k2 == PathRoot::Universe)
+ k2 = PathRoot::Top;
+ int c = int(k1) - int(k2);
if (c != 0)
return c;
- return p1.data.root.contextName.compare(p2.data.root.contextName);
+ return std::get<Root>(p1.m_data).contextName.compare(std::get<Root>(p2.m_data).contextName);
}
case Kind::Current:
{
- int c = int(p1.data.current.contextKind) - int(p2.data.current.contextKind);
+ int c = int(std::get<Current>(p1.m_data).contextKind)
+ - int(std::get<Current>(p2.m_data).contextKind);
if (c != 0)
return c;
- return p1.data.current.contextName.compare(p2.data.current.contextName);
+ return std::get<Current>(p1.m_data).contextName
+ .compare(std::get<Current>(p2.m_data).contextName);
}
case Kind::Any:
return 0;
case Kind::Filter:
{
- int c = p1.data.filter.filterDescription.compare(p2.data.filter.filterDescription);
+ int c = std::get<Filter>(p1.m_data).filterDescription
+ .compare(std::get<Filter>(p2.m_data).filterDescription);
if (c != 0)
return c;
- if (p1.data.filter.filterDescription.startsWith(u"<")) {
+ if (std::get<Filter>(p1.m_data).filterDescription.startsWith(u"<")) {
// assuming non comparable native code (target comparison is not portable)
auto pp1 = &p1;
auto pp2 = &p2;
@@ -203,24 +168,27 @@ int PathComponent::cmp(const PathComponent &p1, const PathComponent &p2)
return 0;
}
-PathComponent Path::component(int i) const
+} // namespace PathEls
+
+const PathEls::PathComponent &Path::component(int i) const
{
+ static Component emptyComponent;
if (i < 0)
i += m_length;
if (i >= m_length || i < 0) {
Q_ASSERT(false && "index out of bounds");
- return Component();
+ return emptyComponent;
}
i = i - m_length - m_endOffset;
auto data = m_data.get();
while (data) {
- i += data->components.length();
+ i += data->components.size();
if (i >= 0)
- return data->components.at(i);
+ return std::as_const(data)->components[i];
data = data->parent.get();
}
Q_ASSERT(false && "Invalid data reached while resolving a seemengly valid index in Path (inconsisten Path object)");
- return Component();
+ return emptyComponent;
}
Path Path::operator[](int i) const
@@ -233,10 +201,20 @@ QQmlJS::Dom::Path::operator bool() const
return length() != 0;
}
+PathIterator Path::begin() const
+{
+ return PathIterator{*this};
+}
+
+PathIterator Path::end() const
+{
+ return PathIterator();
+}
+
PathRoot Path::headRoot() const
{
- auto comp = component(0);
- if (Root const * r = comp.base()->asRoot())
+ auto &comp = component(0);
+ if (PathEls::Root const * r = comp.asRoot())
return r->contextKind;
return PathRoot::Other;
}
@@ -244,13 +222,15 @@ PathRoot Path::headRoot() const
PathCurrent Path::headCurrent() const
{
auto comp = component(0);
- if (Current const * c = comp.base()->asCurrent())
+ if (PathEls::Current const * c = comp.asCurrent())
return c->contextKind;
return PathCurrent::Other;
}
Path::Kind Path::headKind() const
{
+ if (m_length == 0)
+ return Path::Kind::Empty;
return component(0).kind();
}
@@ -269,10 +249,10 @@ index_type Path::headIndex(index_type defaultValue) const
return component(0).index(defaultValue);
}
-function<bool (DomItem)> Path::headFilter() const
+function<bool(const DomItem &)> Path::headFilter() const
{
- auto comp = component(0);
- if (Filter const * f = comp.base()->asFilter()) {
+ auto &comp = component(0);
+ if (PathEls::Filter const * f = comp.asFilter()) {
return f->filterFunction;
}
return {};
@@ -300,7 +280,7 @@ Source Path::split() const
return Source{Path(), *this};
}
-bool inQString(QStringView el, QString base)
+bool inQString(QStringView el, const QString &base)
{
if (quintptr(base.constData()) > quintptr(el.begin())
|| quintptr(base.constData() + base.size()) < quintptr(el.begin()))
@@ -309,7 +289,7 @@ bool inQString(QStringView el, QString base)
return diff >= 0 && diff < base.size();
}
-bool inQString(QString el, QString base)
+bool inQString(const QString &el, const QString &base)
{
if (quintptr(base.constData()) > quintptr(el.constData())
|| quintptr(base.constData() + base.size()) < quintptr(el.constData()))
@@ -318,7 +298,7 @@ bool inQString(QString el, QString base)
return diff >= 0 && diff < base.size() && diff + el.size() < base.size();
}
-Path Path::fromString(QStringView s, ErrorHandler errorHandler)
+Path Path::fromString(QStringView s, const ErrorHandler &errorHandler)
{
if (s.isEmpty())
return Path();
@@ -332,51 +312,51 @@ Path Path::fromString(QStringView s, ErrorHandler errorHandler)
const QChar backslash = QChar::fromLatin1('\\');
const QChar underscore = QChar::fromLatin1('_');
const QChar tilda = QChar::fromLatin1('~');
- for (int i=0; i < s.length(); ++i)
+ for (int i=0; i < s.size(); ++i)
if (s.at(i) == lsBrace || s.at(i) == dot)
++len;
QVector<Component> components;
components.reserve(len);
int i = 0;
int i0 = 0;
- ParserState state = ParserState::Start;
+ PathEls::ParserState state = PathEls::ParserState::Start;
QStringList strVals;
- while (i < s.length()) {
+ while (i < s.size()) {
// skip space
- while (i < s.length() && s.at(i).isSpace())
+ while (i < s.size() && s.at(i).isSpace())
++i;
- if (i >= s.length())
+ if (i >= s.size())
break;
QChar c = s.at(i++);
switch (state) {
- case ParserState::Start:
+ case PathEls::ParserState::Start:
if (c == dollar) {
i0 = i;
- while (i < s.length() && s.at(i).isLetterOrNumber()){
+ while (i < s.size() && s.at(i).isLetterOrNumber()){
++i;
}
- components.append(Component(Root(s.mid(i0,i-i0))));
- state = ParserState::End;
+ components.append(Component(PathEls::Root(s.mid(i0,i-i0))));
+ state = PathEls::ParserState::End;
} else if (c == at) {
i0 = i;
- while (i < s.length() && s.at(i).isLetterOrNumber()){
+ while (i < s.size() && s.at(i).isLetterOrNumber()){
++i;
}
- components.append(Component(Current(s.mid(i0,i-i0))));
- state = ParserState::End;
+ components.append(Component(PathEls::Current(s.mid(i0,i-i0))));
+ state = PathEls::ParserState::End;
} else if (c.isLetter()) {
myErrors().warning(tr("Field expressions should start with a dot, even when at the start of the path %1.")
.arg(s)).handle(errorHandler);
return Path();
} else {
--i;
- state = ParserState::End;
+ state = PathEls::ParserState::End;
}
break;
- case ParserState::IndexOrKey:
+ case PathEls::ParserState::IndexOrKey:
if (c.isDigit()) {
i0 = i-1;
- while (i < s.length() && s.at(i).isDigit())
+ while (i < s.size() && s.at(i).isDigit())
++i;
bool ok;
components.append(Component(static_cast<index_type>(s.mid(i0,i-i0).toString()
@@ -388,22 +368,19 @@ Path Path::fromString(QStringView s, ErrorHandler errorHandler)
}
} else if (c.isLetter() || c == tilda || c == underscore) {
i0 = i-1;
- while (i < s.length() && (s.at(i).isLetterOrNumber() || s.at(i) == underscore || s.at(i) == tilda))
+ while (i < s.size() && (s.at(i).isLetterOrNumber() || s.at(i) == underscore || s.at(i) == tilda))
++i;
- components.append(Component(Key(s.mid(i0, i-i0))));
+ components.append(Component(PathEls::Key(s.mid(i0, i - i0).toString())));
} else if (c == quote) {
i0 = i;
QString strVal;
- QStringView key;
- bool needsConversion = false;
bool properEnd = false;
- while (i < s.length()) {
+ while (i < s.size()) {
c = s.at(i);
if (c == quote) {
properEnd = true;
break;
} else if (c == backslash) {
- needsConversion = true;
strVal.append(s.mid(i0, i - i0).toString());
c = s.at(++i);
i0 = i + 1;
@@ -419,33 +396,27 @@ Path Path::fromString(QStringView s, ErrorHandler errorHandler)
++i;
}
if (properEnd) {
- if (needsConversion) {
- strVal.append(s.mid(i0, i - i0).toString());
- strVals.append(strVal);
- key=strVal;
- } else {
- key = s.mid(i0, i - i0);
- }
+ strVal.append(s.mid(i0, i - i0).toString());
++i;
} else {
myErrors().error(tr("Unclosed quoted string at char %1.")
.arg(QString::number(i - 1))).handle(errorHandler);
return Path();
}
- components.append(Key(key));
+ components.append(PathEls::Key(strVal));
} else if (c == QChar::fromLatin1('*')) {
- components.append(Component(Any()));
+ components.append(Component(PathEls::Any()));
} else if (c == QChar::fromLatin1('?')) {
- while (i < s.length() && s.at(i).isSpace())
+ while (i < s.size() && s.at(i).isSpace())
++i;
- if (i >= s.length() || s.at(i) != QChar::fromLatin1('(')) {
+ if (i >= s.size() || s.at(i) != QChar::fromLatin1('(')) {
myErrors().error(tr("Expected a brace in filter after the question mark (at char %1).")
.arg(QString::number(i))).handle(errorHandler);
return Path();
}
i0 = ++i;
- while (i < s.length() && s.at(i) != QChar::fromLatin1(')')) ++i; // check matching braces when skipping??
- if (i >= s.length() || s.at(i) != QChar::fromLatin1(')')) {
+ while (i < s.size() && s.at(i) != QChar::fromLatin1(')')) ++i; // check matching braces when skipping??
+ if (i >= s.size() || s.at(i) != QChar::fromLatin1(')')) {
myErrors().error(tr("Expected a closing brace in filter after the question mark (at char %1).")
.arg(QString::number(i))).handle(errorHandler);
return Path();
@@ -459,32 +430,32 @@ Path Path::fromString(QStringView s, ErrorHandler errorHandler)
.arg(c).arg(i-1)).handle(errorHandler);
return Path();
}
- while (i < s.length() && s.at(i).isSpace()) ++i;
- if (i >= s.length() || s.at(i) != rsBrace) {
+ while (i < s.size() && s.at(i).isSpace()) ++i;
+ if (i >= s.size() || s.at(i) != rsBrace) {
myErrors().error(tr("square braces misses closing brace at char %1.")
.arg(QString::number(i))).handle(errorHandler);
return Path();
} else {
++i;
}
- state = ParserState::End;
+ state = PathEls::ParserState::End;
break;
- case ParserState::End:
+ case PathEls::ParserState::End:
if (c == dot) {
- while (i < s.length() && s.at(i).isSpace()) ++i;
- if (i == s.length()) {
+ while (i < s.size() && s.at(i).isSpace()) ++i;
+ if (i == s.size()) {
components.append(Component());
- state = ParserState::End;
+ state = PathEls::ParserState::End;
} else if (s.at(i).isLetter() || s.at(i) == underscore || s.at(i) == tilda) {
i0 = i;
- while (i < s.length() && (s.at(i).isLetterOrNumber() || s.at(i) == underscore || s.at(i) == tilda)) {
+ while (i < s.size() && (s.at(i).isLetterOrNumber() || s.at(i) == underscore || s.at(i) == tilda)) {
++i;
}
- components.append(Component(Field(s.mid(i0,i-i0))));
- state = ParserState::End;
+ components.append(Component(PathEls::Field(s.mid(i0,i-i0))));
+ state = PathEls::ParserState::End;
} else if (s.at(i).isDigit()) {
i0 = i;
- while (i < s.length() && s.at(i).isDigit()){
+ while (i < s.size() && s.at(i).isDigit()){
++i;
}
bool ok;
@@ -495,27 +466,27 @@ Path Path::fromString(QStringView s, ErrorHandler errorHandler)
.arg(QString::number(i0))).handle(errorHandler);
return Path();
} else {
- myErrors().hint(tr("Index should use square brackets and not a dot (at char %1).")
+ myErrors().info(tr("Index should use square brackets and not a dot (at char %1).")
.arg(QString::number(i0))).handle(errorHandler);
}
- state = ParserState::End;
+ state = PathEls::ParserState::End;
} else if (s.at(i) == dot || s.at(i) == lsBrace) {
components.append(Component());
- state = ParserState::End;
+ state = PathEls::ParserState::End;
} else if (s.at(i) == at) {
i0 = ++i;
- while (i < s.length() && s.at(i).isLetterOrNumber()){
+ while (i < s.size() && s.at(i).isLetterOrNumber()){
++i;
}
- components.append(Component(Current(s.mid(i0,i-i0))));
- state = ParserState::End;
+ components.append(Component(PathEls::Current(s.mid(i0,i-i0))));
+ state = PathEls::ParserState::End;
} else if (s.at(i) == dollar) {
i0 = ++i;
- while (i < s.length() && s.at(i).isLetterOrNumber()){
+ while (i < s.size() && s.at(i).isLetterOrNumber()){
++i;
}
- components.append(Component(Root(s.mid(i0,i-i0))));
- state = ParserState::End;
+ components.append(Component(PathEls::Root(s.mid(i0,i-i0))));
+ state = PathEls::ParserState::End;
} else {
c=s.at(i);
myErrors().error(tr("Unexpected character '%1' after dot (at char %2).")
@@ -524,7 +495,7 @@ Path Path::fromString(QStringView s, ErrorHandler errorHandler)
return Path();
}
} else if (c == lsBrace) {
- state = ParserState::IndexOrKey;
+ state = PathEls::ParserState::IndexOrKey;
} else {
myErrors().error(tr("Unexpected character '%1' after end of component (char %2).")
.arg(QStringView(&c,1))
@@ -535,163 +506,184 @@ Path Path::fromString(QStringView s, ErrorHandler errorHandler)
}
}
switch (state) {
- case ParserState::Start:
+ case PathEls::ParserState::Start:
return Path();
- case ParserState::IndexOrKey:
+ case PathEls::ParserState::IndexOrKey:
errorHandler(myErrors().error(tr("unclosed square brace at end.")));
return Path();
- case ParserState::End:
- return Path(0, components.length(), std::make_shared<PathData>(strVals, components));
+ case PathEls::ParserState::End:
+ return Path(0, components.size(), std::make_shared<PathEls::PathData>(
+ strVals, components));
}
Q_ASSERT(false && "Unexpected state in Path::fromString");
return Path();
}
-Path Path::root(PathRoot s)
+Path Path::Root(PathRoot s)
{
- return Path(0,1,std::make_shared<PathData>(QStringList(), QVector<Component>(1,Component(Root(s)))));
+ return Path(0,1,std::make_shared<PathEls::PathData>(
+ QStringList(), QVector<Component>(1,Component(PathEls::Root(s)))));
}
-Path Path::root(QString s)
+Path Path::Root(const QString &s)
{
- return Path(0,1,std::make_shared<PathData>(QStringList(s), QVector<Component>(1,Component(Root(s)))));
+ return Path(0,1,std::make_shared<PathEls::PathData>(
+ QStringList(s), QVector<Component>(1,Component(PathEls::Root(s)))));
}
-Path Path::index(index_type i)
+Path Path::Index(index_type i)
{
- return Path(0,1,std::make_shared<PathData>(QStringList(), QVector<Component>(1,Component(Index(i)))));
+ return Path(0,1,std::make_shared<PathEls::PathData>(
+ QStringList(), QVector<Component>(1,Component(PathEls::Index(i)))));
}
-Path Path::root(QStringView s)
+Path Path::Root(QStringView s)
{
- return Path(0,1,std::make_shared<PathData>(QStringList(), QVector<Component>(1,Component(Root(s)))));
+ return Path(0,1,std::make_shared<PathEls::PathData>(
+ QStringList(), QVector<Component>(1,Component(PathEls::Root(s)))));
}
-Path Path::field(QStringView s)
+Path Path::Field(QStringView s)
{
- return Path(0,1,std::make_shared<PathData>(QStringList(), QVector<Component>(1,Component(Field(s)))));
+ return Path(0,1,std::make_shared<PathEls::PathData>(
+ QStringList(), QVector<Component>(1,Component(PathEls::Field(s)))));
}
-Path Path::field(QString s)
+Path Path::Field(const QString &s)
{
- return Path(0,1,std::make_shared<PathData>(QStringList(s), QVector<Component>(1,Component(Field(s)))));
+ return Path(0,1,std::make_shared<PathEls::PathData>(
+ QStringList(s), QVector<Component>(1,Component(PathEls::Field(s)))));
}
-Path Path::key(QStringView s)
+Path Path::Key(QStringView s)
{
- return Path(0,1,std::make_shared<PathData>(QStringList(), QVector<Component>(1,Component(Key(s)))));
+ return Path(
+ 0, 1,
+ std::make_shared<PathEls::PathData>(
+ QStringList(), QVector<Component>(1, Component(PathEls::Key(s.toString())))));
}
-Path Path::key(QString s)
+Path Path::Key(const QString &s)
{
- return Path(0,1,std::make_shared<PathData>(QStringList(s), QVector<Component>(1,Component(Key(s)))));
+ return Path(0, 1,
+ std::make_shared<PathEls::PathData>(
+ QStringList(), QVector<Component>(1, Component(PathEls::Key(s)))));
}
-Path Path::current(PathCurrent s)
+Path Path::Current(PathCurrent s)
{
- return Path(0,1,std::make_shared<PathData>(QStringList(), QVector<Component>(1,Component(Current(s)))));
+ return Path(0,1,std::make_shared<PathEls::PathData>(
+ QStringList(), QVector<Component>(1,Component(PathEls::Current(s)))));
}
-Path Path::current(QString s)
+Path Path::Current(const QString &s)
{
- return Path(0,1,std::make_shared<PathData>(QStringList(s), QVector<Component>(1,Component(Current(s)))));
+ return Path(0,1,std::make_shared<PathEls::PathData>(
+ QStringList(s), QVector<Component>(1,Component(PathEls::Current(s)))));
}
-Path Path::current(QStringView s)
+Path Path::Current(QStringView s)
{
- return Path(0,1,std::make_shared<PathData>(QStringList(), QVector<Component>(1,Component(Current(s)))));
+ return Path(0,1,std::make_shared<PathEls::PathData>(
+ QStringList(), QVector<Component>(1,Component(PathEls::Current(s)))));
}
-Path Path::empty()
+Path Path::Empty()
{
return Path();
}
-Path Path::subEmpty() const
+Path Path::empty() const
{
if (m_endOffset != 0)
- return noEndOffset().subEmpty();
- return Path(0,m_length+1,std::make_shared<PathData>(QStringList(), QVector<Component>(1,Component()), m_data));
+ return noEndOffset().empty();
+ return Path(0,m_length+1,std::make_shared<PathEls::PathData>(
+ QStringList(), QVector<Component>(1,Component()), m_data));
}
-Path Path::subField(QString name) const
+Path Path::field(const QString &name) const
{
- auto res = subField(QStringView(name));
+ auto res = field(QStringView(name));
res.m_data->strData.append(name);
return res;
}
-Path Path::subField(QStringView name) const
+Path Path::field(QStringView name) const
{
if (m_endOffset != 0)
- return noEndOffset().subField(name);
- return Path(0,m_length+1,std::make_shared<PathData>(QStringList(), QVector<Component>(1,Component(Field(name))), m_data));
+ return noEndOffset().field(name);
+ return Path(0,m_length+1,std::make_shared<PathEls::PathData>(
+ QStringList(), QVector<Component>(1,Component(PathEls::Field(name))), m_data));
}
-Path Path::subKey(QString name) const
+Path Path::key(const QString &name) const
{
- auto res = subKey(QStringView(name));
- res.m_data->strData.append(name);
- return res;
+ if (m_endOffset != 0)
+ return noEndOffset().key(name);
+ return Path(0,m_length+1,std::make_shared<PathEls::PathData>(
+ QStringList(), QVector<Component>(1,Component(PathEls::Key(name))), m_data));
}
-Path Path::subKey(QStringView name) const
+Path Path::key(QStringView name) const
{
- if (m_endOffset != 0)
- return noEndOffset().subKey(name);
- return Path(0,m_length+1,std::make_shared<PathData>(QStringList(), QVector<Component>(1,Component(Key(name))), m_data));
+ return key(name.toString());
}
-Path Path::subIndex(index_type i) const
+Path Path::index(index_type i) const
{
if (m_endOffset != 0)
- return noEndOffset().subIndex(i);
- return Path(0,m_length+1,std::make_shared<PathData>(QStringList(), QVector<Component>(1,Component(i)), m_data));
+ return noEndOffset().index(i);
+ return Path(0,m_length+1,std::make_shared<PathEls::PathData>(
+ QStringList(), QVector<Component>(1,Component(i)), m_data));
}
-Path Path::subAny() const
+Path Path::any() const
{
if (m_endOffset != 0)
- return noEndOffset().subAny();
- return Path(0,m_length+1,std::make_shared<PathData>(QStringList(), QVector<Component>(1,Component(Any())), m_data));
+ return noEndOffset().any();
+ return Path(0,m_length+1,std::make_shared<PathEls::PathData>(
+ QStringList(), QVector<Component>(1,Component(PathEls::Any())), m_data));
}
-Path Path::subFilter(function<bool (DomItem)> filter, QString desc) const
+Path Path::filter(const function<bool(const DomItem &)> &filterF, const QString &desc) const
{
- auto res = subFilter(filter, QStringView(desc));
+ auto res = filter(filterF, QStringView(desc));
res.m_data->strData.append(desc);
return res;
}
-Path Path::subFilter(function<bool (DomItem)> filter, QStringView desc) const
+Path Path::filter(const function<bool(const DomItem &)> &filter, QStringView desc) const
{
if (m_endOffset != 0)
- return noEndOffset().subFilter(filter, desc);
- return Path(0,m_length+1,std::make_shared<PathData>(QStringList(), QVector<Component>(1,Component(Filter(filter, desc))), m_data));
+ return noEndOffset().filter(filter, desc);
+ return Path(0,m_length+1,std::make_shared<PathEls::PathData>(
+ QStringList(), QVector<Component>(1,Component(PathEls::Filter(filter, desc))), m_data));
}
-Path Path::subCurrent(PathCurrent s) const
+Path Path::current(PathCurrent s) const
{
- return Path(0,m_length+1,std::make_shared<PathData>(QStringList(), QVector<Component>(1,Component(Current(s))), m_data));
+ return Path(0,m_length+1,std::make_shared<PathEls::PathData>(
+ QStringList(), QVector<Component>(1,Component(PathEls::Current(s))), m_data));
}
-Path Path::subCurrent(QString s) const
+Path Path::current(const QString &s) const
{
- auto res = subCurrent(QStringView(s));
+ auto res = current(QStringView(s));
res.m_data->strData.append(s);
return res;
}
-Path Path::subCurrent(QStringView s) const
+Path Path::current(QStringView s) const
{
if (m_endOffset != 0)
- return noEndOffset().subCurrent(s);
- return Path(0,m_length+1,std::make_shared<PathData>(QStringList(), QVector<Component>(1,Component(Current(s))), m_data));
+ return noEndOffset().current(s);
+ return Path(0,m_length+1,std::make_shared<PathEls::PathData>(
+ QStringList(), QVector<Component>(1,Component(PathEls::Current(s))), m_data));
}
-Path Path::subPath(Path toAdd, bool avoidToAddAsBase) const
+Path Path::path(const Path &toAdd, bool avoidToAddAsBase) const
{
if (toAdd.length() == 0)
return *this;
@@ -705,7 +697,7 @@ Path Path::subPath(Path toAdd, bool avoidToAddAsBase) const
if (resLength == thisExtended.length())
return thisExtended;
else
- return thisExtended.subPath(toAdd.mid(added.length(), resLength - thisExtended.length()));
+ return thisExtended.path(toAdd.mid(added.length(), resLength - thisExtended.length()));
}
}
if (!avoidToAddAsBase) {
@@ -737,9 +729,9 @@ Path Path::subPath(Path toAdd, bool avoidToAddAsBase) const
}
data = toAdd.m_data.get();
while (data) {
- for (int ij = 0; ij < data->strData.length(); ++ij) {
+ for (int ij = 0; ij < data->strData.size(); ++ij) {
bool hasAlready = false;
- for (int ii = 0; ii < myStrs.length() && !hasAlready; ++ii)
+ for (int ii = 0; ii < myStrs.size() && !hasAlready; ++ii)
hasAlready = inQString(data->strData[ij], myStrs[ii]);
if (!hasAlready)
addedStrs.append(data->strData[ij]);
@@ -752,7 +744,7 @@ Path Path::subPath(Path toAdd, bool avoidToAddAsBase) const
components.append(toAdd.component(i));
QStringView compStrView = toAdd.component(i).stringView();
if (!compStrView.isEmpty()) {
- for (int j = 0; j < addedStrs.length(); ++j) {
+ for (int j = 0; j < addedStrs.size(); ++j) {
if (inQString(compStrView, addedStrs[j])) {
toAddStrs.append(addedStrs[j]);
addedStrs.removeAt(j);
@@ -761,8 +753,8 @@ Path Path::subPath(Path toAdd, bool avoidToAddAsBase) const
}
}
}
- return Path(0, m_length + toAdd.length(),
- std::make_shared<PathData>(toAddStrs, components, ((m_endOffset == 0) ? m_data : noEndOffset().m_data)));
+ return Path(0, m_length + toAdd.length(), std::make_shared<PathEls::PathData>(
+ toAddStrs, components, ((m_endOffset == 0) ? m_data : noEndOffset().m_data)));
}
Path Path::expandFront() const
@@ -770,7 +762,7 @@ Path Path::expandFront() const
int newLen = 0;
auto data = m_data.get();
while (data) {
- newLen += data->components.length();
+ newLen += data->components.size();
data = data->parent.get();
}
newLen -= m_endOffset;
@@ -817,7 +809,7 @@ int Path::cmp(const Path &p1, const Path &p2)
return 0;
}
-Path::Path(quint16 endOffset, quint16 length, std::shared_ptr<PathData> data)
+Path::Path(quint16 endOffset, quint16 length, const std::shared_ptr<PathEls::PathData> &data)
:m_endOffset(endOffset), m_length(length), m_data(data)
{
}
@@ -830,29 +822,90 @@ Path Path::noEndOffset() const
return *this;
// peel back
qint16 endOffset = m_endOffset;
- std::shared_ptr<PathData> lastData = m_data;
- while (lastData && endOffset >= lastData->components.length()) {
- endOffset -= lastData->components.length();
+ std::shared_ptr<PathEls::PathData> lastData = m_data;
+ while (lastData && endOffset >= lastData->components.size()) {
+ endOffset -= lastData->components.size();
lastData = lastData->parent;
}
if (endOffset > 0) {
Q_ASSERT(lastData && "Internal problem, reference to non existing PathData");
- return Path(0, m_length, std::make_shared<PathData>(lastData->strData, lastData->components.mid(0, lastData->components.length() - endOffset), lastData->parent));
+ return Path(0, m_length, std::make_shared<PathEls::PathData>(
+ lastData->strData, lastData->components.mid(0, lastData->components.size() - endOffset), lastData->parent));
}
return Path(0, m_length, lastData);
}
+Path Path::appendComponent(const PathEls::PathComponent &c)
+{
+ if (m_endOffset != 0) {
+ Path newP = noEndOffset();
+ return newP.appendComponent(c);
+ }
+ if (m_data && m_data.use_count() != 1) {
+ // create a new path (otherwise paths linking to this will change)
+ Path newP(c);
+ newP.m_data->parent = m_data;
+ newP.m_length = static_cast<quint16>(m_length + 1);
+ return newP;
+ }
+ auto my_data =
+ (m_data ? m_data
+ : std::make_shared<PathEls::PathData>(QStringList(),
+ QVector<PathEls::PathComponent>()));
+ switch (c.kind()) {
+ case PathEls::Kind::Any:
+ case PathEls::Kind::Empty:
+ case PathEls::Kind::Index:
+ // no string
+ case PathEls::Kind::Field:
+ // string assumed to stay valid (Fields::...)
+ my_data->components.append(c);
+ break;
+ case PathEls::Kind::Current:
+ if (c.asCurrent()->contextKind == PathCurrent::Other) {
+ my_data->strData.append(c.asCurrent()->contextName.toString());
+ my_data->components.append(PathEls::Current(my_data->strData.last()));
+ } else {
+ my_data->components.append(c);
+ }
+ break;
+ case PathEls::Kind::Filter:
+ if (!c.asFilter()->filterDescription.isEmpty()) {
+ my_data->strData.append(c.asFilter()->filterDescription.toString());
+ my_data->components.append(
+ PathEls::Filter(c.asFilter()->filterFunction, my_data->strData.last()));
+ } else {
+ my_data->components.append(c);
+ }
+ break;
+ case PathEls::Kind::Key:
+ my_data->components.append(c);
+ break;
+ case PathEls::Kind::Root:
+ if (c.asRoot()->contextKind == PathRoot::Other) {
+ my_data->strData.append(c.asRoot()->contextName.toString());
+ my_data->components.append(PathEls::Root(my_data->strData.last()));
+ } else {
+ my_data->components.append(c);
+ }
+ break;
+ }
+ if (m_data)
+ m_endOffset = 1;
+ return Path { 0, static_cast<quint16>(m_length + 1), my_data };
+}
+
ErrorGroups Path::myErrors()
{
static ErrorGroups res = {{NewErrorGroup("PathParsing")}};
return res;
}
-void Path::dump(Sink sink) const
+void Path::dump(const Sink &sink) const
{
bool first = true;
for (int i = 0; i < m_length; ++i) {
- auto c = component(i);
+ auto &c = component(i);
if (!c.hasSquareBrackets()) {
if (!first || (c.kind() != Kind::Root && c.kind() != Kind::Current))
sink(u".");
@@ -871,17 +924,17 @@ QString Path::toString() const
return res;
}
-Path Path::dropFront() const
+Path Path::dropFront(int n) const
{
- if (m_length > 0)
- return Path(m_endOffset, m_length - 1, m_data);
+ if (m_length > n && n >= 0)
+ return Path(m_endOffset, m_length - n, m_data);
return Path();
}
-Path Path::dropTail() const
+Path Path::dropTail(int n) const
{
- if (m_length > 0)
- return Path(m_endOffset + 1, m_length - 1, m_data);
+ if (m_length > n && n >= 0)
+ return Path(m_endOffset + n, m_length - n, m_data);
return Path();
}
@@ -899,7 +952,7 @@ Path Path::mid(int offset) const
return mid(offset, m_length - offset);
}
-Path Path::fromString(QString s, ErrorHandler errorHandler)
+Path Path::fromString(const QString &s, const ErrorHandler &errorHandler)
{
Path res = fromString(QStringView(s), errorHandler);
if (res.m_data)
@@ -910,3 +963,5 @@ Path Path::fromString(QString s, ErrorHandler errorHandler)
} // end namespace Dom
} // end namespace QQmlJS
QT_END_NAMESPACE
+
+#include "moc_qqmldompath_p.cpp"
diff --git a/src/qmldom/qqmldompath_p.h b/src/qmldom/qqmldompath_p.h
index 839a34a233..1a5af85e8e 100644
--- a/src/qmldom/qqmldompath_p.h
+++ b/src/qmldom/qqmldompath_p.h
@@ -1,40 +1,6 @@
-/****************************************************************************
-**
-** 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$
-**/
+// Copyright (C) 2020 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+
#ifndef QMLDOM_PATH_H
#define QMLDOM_PATH_H
@@ -103,82 +69,72 @@ class Filter;
class Base {
public:
- virtual ~Base() = default;
- virtual Kind kind() const = 0;
- virtual QString name() const = 0;
- virtual bool checkName(QStringView s) const = 0;
- virtual QStringView stringView() const { return QStringView(); }
- virtual index_type index(index_type defaultValue=-1) const { return defaultValue; }
-
- virtual void dump(Sink sink) const;
- virtual bool hasSquareBrackets() const { return false; }
-
- // casting, could use optional, but that is c++17...
- virtual const Empty *asEmpty() const { return nullptr; }
- virtual const Field *asField() const { return nullptr; }
- virtual const Index *asIndex() const { return nullptr; }
- virtual const Key *asKey() const { return nullptr; }
- virtual const Root *asRoot() const { return nullptr; }
- virtual const Current *asCurrent() const { return nullptr; }
- virtual const Any *asAny() const { return nullptr; }
- virtual const Filter *asFilter() const { return nullptr; }
+ QStringView stringView() const { return QStringView(); }
+ index_type index(index_type defaultValue = -1) const { return defaultValue; }
+ bool hasSquareBrackets() const { return false; }
+
+protected:
+ void dump(const Sink &sink, const QString &name, bool hasSquareBrackets) const;
};
-class Empty: public Base {
+class Empty final : public Base
+{
public:
- Kind kind() const override { return Kind::Empty; }
- QString name() const override { return QString(); }
- bool checkName(QStringView s) const override { return s.isEmpty(); }
- const Empty * asEmpty() const override { return this; }
+ Empty() = default;
+ QString name() const { return QString(); }
+ bool checkName(QStringView s) const { return s.isEmpty(); }
+ void dump(const Sink &sink) const { Base::dump(sink, name(), hasSquareBrackets()); }
};
-class Field: public Base {
+class Field final : public Base
+{
public:
+ Field() = default;
Field(QStringView n): fieldName(n) {}
- Kind kind() const override { return Kind::Field; }
- QString name() const override { return fieldName.toString(); }
- bool checkName(QStringView s) const override { return s == fieldName; }
- QStringView stringView() const override { return fieldName; }
- const Field * asField() const override { return this; }
- void dump(Sink sink) const override { sink(fieldName); }
+ QString name() const { return fieldName.toString(); }
+ bool checkName(QStringView s) const { return s == fieldName; }
+ QStringView stringView() const { return fieldName; }
+ void dump(const Sink &sink) const { sink(fieldName); }
QStringView fieldName;
};
-class Index: public Base {
+class Index final : public Base
+{
public:
+ Index() = default;
Index(index_type i): indexValue(i) {}
- Kind kind() const override { return Kind::Index; }
- QString name() const override { return QString::number(indexValue); }
- bool checkName(QStringView s) const override { return s == name(); }
- index_type index(index_type = -1) const override { return indexValue; }
- bool hasSquareBrackets() const override { return true; }
- const Index * asIndex() const override { return this; }
-
- index_type indexValue;
+ QString name() const { return QString::number(indexValue); }
+ bool checkName(QStringView s) const { return s == name(); }
+ index_type index(index_type = -1) const { return indexValue; }
+ void dump(const Sink &sink) const { Base::dump(sink, name(), hasSquareBrackets()); }
+ bool hasSquareBrackets() const { return true; }
+
+ index_type indexValue = -1;
};
-class Key: public Base {
+class Key final : public Base
+{
public:
- Key(QStringView n): keyValue(n) {}
- Kind kind() const override { return Kind::Key; }
- QString name() const override { return keyValue.toString(); }
- bool checkName(QStringView s) const override { return s == keyValue; }
- QStringView stringView() const override { return keyValue; }
- void dump(Sink sink) const override {
+ Key() = default;
+ Key(const QString &n) : keyValue(n) { }
+ QString name() const { return keyValue; }
+ bool checkName(QStringView s) const { return s == keyValue; }
+ QStringView stringView() const { return keyValue; }
+ void dump(const Sink &sink) const {
sink(u"[");
sinkEscaped(sink, keyValue);
sink(u"]");
}
- bool hasSquareBrackets() const override { return true; }
- const Key * asKey() const override { return this; }
+ bool hasSquareBrackets() const { return true; }
- QStringView keyValue;
+ QString keyValue;
};
-class Root: public Base {
+class Root final : public Base
+{
public:
- Root(): contextKind(PathRoot::Other), contextName() {}
+ Root() = default;
Root(PathRoot r): contextKind(r), contextName() {}
Root(QStringView n) {
QMetaEnum metaEnum = QMetaEnum::fromType<PathRoot>();
@@ -189,8 +145,7 @@ public:
if (contextKind == PathRoot::Other)
contextName = n;
}
- Kind kind() const override { return Kind::Root; }
- QString name() const override {
+ QString name() const {
switch (contextKind) {
case PathRoot::Modules:
return QStringLiteral(u"$modules");
@@ -210,24 +165,22 @@ public:
Q_ASSERT(false && "Unexpected contextKind in name");
return QString();
}
- bool checkName(QStringView s) const override {
+ bool checkName(QStringView s) const {
if (contextKind != PathRoot::Other)
return s.compare(name(), Qt::CaseInsensitive) == 0;
return s.startsWith(QChar::fromLatin1('$')) && s.mid(1) == contextName;
}
- QStringView stringView() const override { return contextName; }
- void dump(Sink sink) const override {
- sink(name());
- }
- const Root *asRoot() const override { return this; }
+ QStringView stringView() const { return contextName; }
+ void dump(const Sink &sink) const { sink(name()); }
- PathRoot contextKind;
+ PathRoot contextKind = PathRoot::Other;
QStringView contextName;
};
-class Current: public Base {
+class Current final : public Base
+{
public:
- Current(): contextName() {}
+ Current() = default;
Current(PathCurrent c): contextKind(c) {}
Current(QStringView n) {
QMetaEnum metaEnum = QMetaEnum::fromType<PathCurrent>();
@@ -238,8 +191,7 @@ public:
if (contextKind == PathCurrent::Other)
contextName = n;
}
- Kind kind() const override { return Kind::Current; }
- QString name() const override {
+ QString name() const {
switch (contextKind) {
case PathCurrent::Other:
return QString::fromUtf8("@").append(contextName.toString());
@@ -267,172 +219,126 @@ public:
Q_ASSERT(false && "Unexpected contextKind in Current::name");
return QString();
}
- bool checkName(QStringView s) const override {
+ bool checkName(QStringView s) const {
if (contextKind != PathCurrent::Other)
return s.compare(name(), Qt::CaseInsensitive) == 0;
return s.startsWith(QChar::fromLatin1('@')) && s.mid(1) == contextName;
}
- QStringView stringView() const override { return contextName; }
- const Current *asCurrent() const override { return this; }
+ QStringView stringView() const { return contextName; }
+ void dump(const Sink &sink) const { Base::dump(sink, name(), hasSquareBrackets()); }
- PathCurrent contextKind;
+ PathCurrent contextKind = PathCurrent::Other;
QStringView contextName;
};
-class Any: public Base {
+class Any final : public Base
+{
public:
- Kind kind() const override { return Kind::Any; }
- QString name() const override { return QLatin1String("*"); }
- bool checkName(QStringView s) const override { return s == u"*"; }
- bool hasSquareBrackets() const override { return true; }
- const Any *asAny() const override { return this; }
+ Any() = default;
+ QString name() const { return QLatin1String("*"); }
+ bool checkName(QStringView s) const { return s == u"*"; }
+ void dump(const Sink &sink) const { Base::dump(sink, name(), hasSquareBrackets()); }
+ bool hasSquareBrackets() const { return true; }
};
-class QMLDOM_EXPORT Filter: public Base {
+class QMLDOM_EXPORT Filter final : public Base
+{
public:
- Filter(std::function<bool(DomItem)> f, QStringView filterDescription = u"<native code filter>");
- Kind kind() const override { return Kind::Filter; }
- QString name() const override;
- bool checkName(QStringView s) const override;
- QStringView stringView() const override { return filterDescription; }
- bool hasSquareBrackets() const override { return true; }
- const Filter *asFilter() const override { return this; }
-
- std::function<bool(DomItem)> filterFunction;
+ Filter() = default;
+ Filter(const std::function<bool(const DomItem &)> &f,
+ QStringView filterDescription = u"<native code filter>");
+ QString name() const;
+ bool checkName(QStringView s) const;
+ QStringView stringView() const { return filterDescription; }
+ void dump(const Sink &sink) const { Base::dump(sink, name(), hasSquareBrackets()); }
+ bool hasSquareBrackets() const { return true; }
+
+ std::function<bool(const DomItem &)> filterFunction;
QStringView filterDescription;
};
class QMLDOM_EXPORT PathComponent {
public:
- PathComponent(): data() {}
- ~PathComponent();
-
- Kind kind() const { return base()->kind(); }
- QString name() const { return base()->name(); };
- bool checkName(QStringView s) const { return base()->checkName(s); }
- QStringView stringView() const { return base()->stringView(); };
- index_type index(index_type defaultValue=-1) const { return base()->index(defaultValue); }
- void dump(Sink sink) const { base()->dump(sink); }
- bool hasSquareBrackets() const { return base()->hasSquareBrackets(); }
-
- const Empty *asEmpty() const { return base()->asEmpty(); }
- const Field *asField() const { return base()->asField(); }
- const Index *asIndex() const { return base()->asIndex(); }
- const Key *asKey() const { return base()->asKey(); }
- const Root *asRoot() const { return base()->asRoot(); }
- const Current *asCurrent() const { return base()->asCurrent(); }
- const Any *asAny() const { return base()->asAny(); }
+ PathComponent() = default;
+ PathComponent(const PathComponent &) = default;
+ PathComponent(PathComponent &&) = default;
+ PathComponent &operator=(const PathComponent &) = default;
+ PathComponent &operator=(PathComponent &&) = default;
+ ~PathComponent() = default;
+
+ Kind kind() const { return Kind(m_data.index()); }
+
+ QString name() const
+ {
+ return std::visit([](auto &&d) { return d.name(); }, m_data);
+ }
+
+ bool checkName(QStringView s) const
+ {
+ return std::visit([s](auto &&d) { return d.checkName(s); }, m_data);
+ }
+
+ QStringView stringView() const
+ {
+ return std::visit([](auto &&d) { return d.stringView(); }, m_data);
+ }
+
+ index_type index(index_type defaultValue=-1) const
+ {
+ return std::visit([defaultValue](auto &&d) { return d.index(defaultValue); }, m_data);
+ }
+
+ void dump(const Sink &sink) const
+ {
+ return std::visit([sink](auto &&d) { return d.dump(sink); }, m_data);
+ }
+
+ bool hasSquareBrackets() const
+ {
+ return std::visit([](auto &&d) { return d.hasSquareBrackets(); }, m_data);
+ }
+
+ const Empty *asEmpty() const { return std::get_if<Empty>(&m_data); }
+ const Field *asField() const { return std::get_if<Field>(&m_data); }
+ const Index *asIndex() const { return std::get_if<Index>(&m_data); }
+ const Key *asKey() const { return std::get_if<Key>(&m_data); }
+ const Root *asRoot() const { return std::get_if<Root>(&m_data); }
+ const Current *asCurrent() const { return std::get_if<Current>(&m_data); }
+ const Any *asAny() const { return std::get_if<Any>(&m_data); }
+ const Filter *asFilter() const { return std::get_if<Filter>(&m_data); }
+
static int cmp(const PathComponent &p1, const PathComponent &p2);
+
+ PathComponent(Empty &&o): m_data(std::move(o)) {}
+ PathComponent(Field &&o): m_data(std::move(o)) {}
+ PathComponent(Index &&o): m_data(std::move(o)) {}
+ PathComponent(Key &&o): m_data(std::move(o)) {}
+ PathComponent(Root &&o): m_data(std::move(o)) {}
+ PathComponent(Current &&o): m_data(std::move(o)) {}
+ PathComponent(Any &&o): m_data(std::move(o)) {}
+ PathComponent(Filter &&o): m_data(std::move(o)) {}
+
private:
friend class QQmlJS::Dom::Path;
friend class QQmlJS::Dom::PathEls::TestPaths;
- PathComponent(const Empty &o): data(o) {}
- PathComponent(const Field &o): data(o) {}
- PathComponent(const Index &o): data(o) {}
- PathComponent(const Key &o): data(o) {}
- PathComponent(const Root &o): data(o) {}
- PathComponent(const Current &o): data(o) {}
- PathComponent(const Any &o): data(o) {}
- PathComponent(const Filter &o): data(o) {}
-
- Base *base() {
- return reinterpret_cast<Base*>(&data);
- }
- const Base *base() const {
- return reinterpret_cast<const Base*>(&data);
- }
- union Data {
- Data(): empty() { }
- Data(const Data &d) {
- switch (d.kind()){
- case Kind::Empty:
- Q_ASSERT(static_cast<void*>(this)==static_cast<void*>(&empty) && "non C++11 compliant compiler");
- new (&empty) Empty(d.empty);
- break;
- case Kind::Field:
- Q_ASSERT(static_cast<void*>(this)==static_cast<void*>(&field) && "non C++11 compliant compiler");
- new (&field) Field(d.field);
- break;
- case Kind::Index:
- Q_ASSERT(static_cast<void*>(this)==static_cast<void*>(&index) && "non C++11 compliant compiler");
- new (&index) Index(d.index);
- break;
- case Kind::Key:
- Q_ASSERT(static_cast<void*>(this)==static_cast<void*>(&key) && "non C++11 compliant compiler");
- new (&key) Key(d.key);
- break;
- case Kind::Root:
- Q_ASSERT(static_cast<void*>(this)==static_cast<void*>(&root) && "non C++11 compliant compiler");
- new (&root) Root(d.root);
- break;
- case Kind::Current:
- Q_ASSERT(static_cast<void*>(this)==static_cast<void*>(&current) && "non C++11 compliant compiler");
- new (&current) Current(d.current);
- break;
- case Kind::Any:
- Q_ASSERT(static_cast<void*>(this)==static_cast<void*>(&any) && "non C++11 compliant compiler");
- new (&any) Any(d.any);
- break;
- case Kind::Filter:
- Q_ASSERT(static_cast<void*>(this)==static_cast<void*>(&filter) && "non C++11 compliant compiler");
- new (&filter) Filter(d.filter);
- break;
- }
- }
- Data(const Empty &o) {
- Q_ASSERT(static_cast<void*>(this)==static_cast<void*>(&empty) && "non C++11 compliant compiler");
- new (&empty) Empty(o);
- }
- Data(const Field &o) {
- Q_ASSERT(static_cast<void*>(this)==static_cast<void*>(&field) && "non C++11 compliant compiler");
- new (&field) Field(o);
- }
- Data(const Index &o){
- Q_ASSERT(static_cast<void*>(this)==static_cast<void*>(&index) && "non C++11 compliant compiler");
- new (&index) Index(o);
- }
- Data(const Key &o) {
- Q_ASSERT(static_cast<void*>(this)==static_cast<void*>(&key) && "non C++11 compliant compiler");
- new (&key) Key(o);
- }
- Data(const Root &o) {
- Q_ASSERT(static_cast<void*>(this)==static_cast<void*>(&root) && "non C++11 compliant compiler");
- new (&root) Root(o);
- }
- Data(const Current &o) {
- Q_ASSERT(static_cast<void*>(this)==static_cast<void*>(&current) && "non C++11 compliant compiler");
- new (&current) Current(o);
- }
- Data(const Any &o) {
- Q_ASSERT(static_cast<void*>(this)==static_cast<void*>(&any) && "non C++11 compliant compiler");
- new (&any) Any(o);
- }
- Data(const Filter &o) {
- Q_ASSERT(static_cast<void*>(this)==static_cast<void*>(&filter) && "non C++11 compliant compiler");
- new (&filter) Filter(o);
- }
- Data &operator=(const Data &d) {
- Q_ASSERT(this != &d);
- this->~Data(); // destruct & construct new...
- new (this)Data(d);
- return *this;
- }
- Kind kind() const {
- return reinterpret_cast<const Base*>(this)->kind();
- }
- ~Data() {
- reinterpret_cast<const Base*>(this)->~Base();
- }
- Empty empty;
- Field field;
- Index index;
- Key key;
- Root root;
- Current current;
- Any any;
- Filter filter;
- } data;
+ using Variant = std::variant<Empty, Field, Index, Key, Root, Current, Any, Filter>;
+
+ template<typename T, Kind K>
+ static constexpr bool variantTypeMatches
+ = std::is_same_v<std::variant_alternative_t<size_t(K), Variant>, T>;
+
+ static_assert(size_t(Kind::Empty) == 0);
+ static_assert(variantTypeMatches<Empty, Kind::Empty>);
+ static_assert(variantTypeMatches<Field, Kind::Field>);
+ static_assert(variantTypeMatches<Key, Kind::Key>);
+ static_assert(variantTypeMatches<Root, Kind::Root>);
+ static_assert(variantTypeMatches<Current, Kind::Current>);
+ static_assert(variantTypeMatches<Any, Kind::Any>);
+ static_assert(variantTypeMatches<Filter, Kind::Filter>);
+ static_assert(std::variant_size_v<Variant> == size_t(Kind::Filter) + 1);
+
+ Variant m_data;
};
inline bool operator==(const PathComponent& lhs, const PathComponent& rhs){ return PathComponent::cmp(lhs,rhs) == 0; }
@@ -444,9 +350,13 @@ inline bool operator>=(const PathComponent& lhs, const PathComponent& rhs){ retu
class PathData {
public:
- PathData(QStringList strData, QVector<PathComponent> components): strData(strData), components(components) {}
- PathData(QStringList strData, QVector<PathComponent> components, std::shared_ptr<PathData> parent):
- strData(strData), components(components), parent(parent) {}
+ PathData(const QStringList &strData, const QVector<PathComponent> &components)
+ : strData(strData), components(components)
+ {}
+ PathData(const QStringList &strData, const QVector<PathComponent> &components,
+ const std::shared_ptr<PathData> &parent)
+ : strData(strData), components(components), parent(parent)
+ {}
QStringList strData;
QVector<PathComponent> components;
@@ -455,149 +365,251 @@ public:
} // namespace PathEls
-#define QMLDOM_USTRING(name) constexpr const auto name = u#name
+#define QMLDOM_USTRING(s) u##s
+#define QMLDOM_FIELD(name) inline constexpr const auto name = QMLDOM_USTRING(#name)
+/*!
+ \internal
+ In an ideal world, the Fields namespace would be an enum, not strings.
+ Use FieldType whenever you expect a static String from the Fields namespace instead of an
+ arbitrary QStringView.
+ */
+using FieldType = QStringView;
// namespace, so it cam be reopened to add more entries
namespace Fields{
-constexpr const auto access = u"access";
-constexpr const auto annotations = u"annotations";
-constexpr const auto attachedType = u"attachedType";
-constexpr const auto attachedTypeName = u"attachedTypeName";
-constexpr const auto autoExport = u"autoExport";
-constexpr const auto base = u"base";
-constexpr const auto bindingType = u"bindingType";
-constexpr const auto bindings = u"bindings";
-constexpr const auto body = u"body";
-constexpr const auto canonicalFilePath = u"canonicalFilePath";
-constexpr const auto canonicalPath = u"canonicalPath";
-constexpr const auto children = u"children";
-constexpr const auto classNames = u"classNames";
-constexpr const auto code = u"code";
-constexpr const auto components = u"components";
-constexpr const auto contents = u"contents";
-constexpr const auto contentsDate = u"contentsDate";
-constexpr const auto currentExposedAt = u"currentExposedAt";
-constexpr const auto currentIsValid = u"currentIsValid";
-constexpr const auto currentItem = u"currentItem";
-constexpr const auto currentRevision = u"currentRevision";
-constexpr const auto defaultPropertyName = u"defaultPropertyName";
-constexpr const auto designerSupported = u"designerSupported";
-constexpr const auto elementCanonicalPath = u"elementCanonicalPath";
-constexpr const auto enumerations = u"enumerations";
-constexpr const auto errors = u"errors";
-constexpr const auto exportSource = u"exportSource";
-constexpr const auto exports = u"exports";
-constexpr const auto extraRequired = u"extraRequired";
-constexpr const auto fileName = u"fileName";
-constexpr const auto get = u"get";
-constexpr const auto globalScopeName = u"globalScopeName";
-constexpr const auto globalScopeWithName = u"globalScopeWithName";
-constexpr const auto hasCallback = u"hasCallback";
-constexpr const auto idStr = u"idStr";
-constexpr const auto ids = u"ids";
-constexpr const auto import = u"import";
-constexpr const auto importId = u"importId";
-constexpr const auto imports = u"imports";
-constexpr const auto inheritVersion = u"inheritVersion";
-constexpr const auto inProgress = u"inProgress";
-constexpr const auto isAlias = u"isAlias";
-constexpr const auto isComposite = u"isComposite";
-constexpr const auto isCreatable = u"isCreatable";
-constexpr const auto isDefaultMember = u"isDefaultMember";
-constexpr const auto isInternal = u"isInternal";
-constexpr const auto isLatest = u"isLatest";
-constexpr const auto isList = u"isList";
-constexpr const auto isPointer = u"isPointer";
-constexpr const auto isRequired = u"isRequired";
-constexpr const auto isSingleton = u"isSingleton";
-constexpr const auto isValid = u"isValid";
-constexpr const auto isWritable = u"isWritable";
-constexpr const auto jsFileWithPath = u"jsFileWithPath";
-constexpr const auto kind = u"kind";
-constexpr const auto lastRevision = u"lastRevision";
-constexpr const auto lastValidRevision = u"lastValidRevision";
-constexpr const auto loadInfo = u"loadInfo";
-constexpr const auto loadOptions = u"loadOptions";
-constexpr const auto loadPaths = u"loadPaths";
-constexpr const auto loadsWithWork = u"loadsWithWork";
-constexpr const auto location = u"location";
-constexpr const auto logicalPath = u"logicalPath";
-constexpr const auto majorVersion = u"majorVersion";
-constexpr const auto metaRevisions = u"metaRevisions";
-constexpr const auto methodType = u"methodType";
-constexpr const auto methods = u"methods";
-constexpr const auto minorVersion = u"minorVersion";
-constexpr const auto moduleIndex = u"moduleIndex";
-constexpr const auto moduleIndexWithUri = u"moduleIndexWithUri";
-constexpr const auto moduleScope = u"moduleScope";
-constexpr const auto nAllLoadedCallbacks = u"nAllLoadedCallbacks";
-constexpr const auto nCallbacks = u"nCallbacks";
-constexpr const auto nLoaded = u"nLoaded";
-constexpr const auto nNotdone = u"nNotdone";
-constexpr const auto name = u"name";
-constexpr const auto nextScope = u"nextScope";
-constexpr const auto objects = u"objects";
-constexpr const auto onAttachedObject = u"onAttachedObject";
-constexpr const auto options = u"options";
-constexpr const auto parameters = u"parameters";
-constexpr const auto parentObject = u"parentObject";
-constexpr const auto path = u"path";
-constexpr const auto plugins = u"plugins";
-constexpr const auto pragma = u"pragma";
-constexpr const auto pragmas = u"pragmas";
-constexpr const auto propertyDef = u"propertyDef";
-constexpr const auto propertyDefRef = u"propertyDefRef";
-constexpr const auto propertyDefs = u"propertyDefs";
-constexpr const auto propertyName = u"propertyName";
-constexpr const auto prototype = u"prototype";
-constexpr const auto qmlDirectoryWithPath = u"qmlDirectoryWithPath";
-constexpr const auto qmlFileWithPath = u"qmlFileWithPath";
-constexpr const auto qmldirFileWithPath = u"qmldirFileWithPath";
-constexpr const auto qmltypesFileWithPath = u"qmltypesFileWithPath";
-constexpr const auto qmltypesFiles = u"qmltypesFiles";
-constexpr const auto queue = u"queue";
-constexpr const auto referredObject = u"referredObject";
-constexpr const auto referredObjectPath = u"referredObjectPath";
-constexpr const auto requestedAt = u"requestedAt";
-constexpr const auto requestingUniverse = u"requestingUniverse";
-constexpr const auto returnType = u"returnType";
-constexpr const auto returnTypeName = u"returnTypeName";
-constexpr const auto rootComponent = u"rootComponent";
-constexpr const auto sources = u"sources";
-constexpr const auto status = u"status";
-constexpr const auto stringValue = u"stringValue";
-constexpr const auto symbols = u"symbols";
-constexpr const auto target = u"target";
-constexpr const auto targetPropertyName = u"targetPropertyName";
-constexpr const auto type = u"type";
-constexpr const auto typeName = u"typeName";
-constexpr const auto types = u"types";
-constexpr const auto universe = u"universe";
-constexpr const auto uri = u"uri";
-constexpr const auto uris = u"uris";
-constexpr const auto validExposedAt = u"validExposedAt";
-constexpr const auto validItem = u"validItem";
-constexpr const auto value = u"value";
-constexpr const auto values = u"values";
-constexpr const auto version = u"version";
-constexpr const auto when = u"when";
-}
+QMLDOM_FIELD(access);
+QMLDOM_FIELD(accessSemantics);
+QMLDOM_FIELD(allSources);
+QMLDOM_FIELD(alternative);
+QMLDOM_FIELD(annotations);
+QMLDOM_FIELD(arguments);
+QMLDOM_FIELD(astComments);
+QMLDOM_FIELD(astRelocatableDump);
+QMLDOM_FIELD(attachedType);
+QMLDOM_FIELD(attachedTypeName);
+QMLDOM_FIELD(autoExports);
+QMLDOM_FIELD(base);
+QMLDOM_FIELD(binaryExpression);
+QMLDOM_FIELD(bindable);
+QMLDOM_FIELD(bindingElement);
+QMLDOM_FIELD(bindingIdentifiers);
+QMLDOM_FIELD(bindingType);
+QMLDOM_FIELD(bindings);
+QMLDOM_FIELD(block);
+QMLDOM_FIELD(body);
+QMLDOM_FIELD(callee);
+QMLDOM_FIELD(canonicalFilePath);
+QMLDOM_FIELD(canonicalPath);
+QMLDOM_FIELD(caseBlock);
+QMLDOM_FIELD(caseClause);
+QMLDOM_FIELD(caseClauses);
+QMLDOM_FIELD(catchBlock);
+QMLDOM_FIELD(catchParameter);
+QMLDOM_FIELD(children);
+QMLDOM_FIELD(classNames);
+QMLDOM_FIELD(code);
+QMLDOM_FIELD(commentedElements);
+QMLDOM_FIELD(comments);
+QMLDOM_FIELD(components);
+QMLDOM_FIELD(condition);
+QMLDOM_FIELD(consequence);
+QMLDOM_FIELD(contents);
+QMLDOM_FIELD(contentsDate);
+QMLDOM_FIELD(cppType);
+QMLDOM_FIELD(currentExposedAt);
+QMLDOM_FIELD(currentIsValid);
+QMLDOM_FIELD(currentItem);
+QMLDOM_FIELD(currentRevision);
+QMLDOM_FIELD(declarations);
+QMLDOM_FIELD(defaultClause);
+QMLDOM_FIELD(defaultPropertyName);
+QMLDOM_FIELD(defaultValue);
+QMLDOM_FIELD(designerSupported);
+QMLDOM_FIELD(elLocation);
+QMLDOM_FIELD(elements);
+QMLDOM_FIELD(elementCanonicalPath);
+QMLDOM_FIELD(enumerations);
+QMLDOM_FIELD(errors);
+QMLDOM_FIELD(exportSource);
+QMLDOM_FIELD(exports);
+QMLDOM_FIELD(expr);
+QMLDOM_FIELD(expression);
+QMLDOM_FIELD(expressionType);
+QMLDOM_FIELD(extensionTypeName);
+QMLDOM_FIELD(fileLocationsTree);
+QMLDOM_FIELD(fileName);
+QMLDOM_FIELD(finallyBlock);
+QMLDOM_FIELD(forStatement);
+QMLDOM_FIELD(fullRegion);
+QMLDOM_FIELD(get);
+QMLDOM_FIELD(globalScopeName);
+QMLDOM_FIELD(globalScopeWithName);
+QMLDOM_FIELD(hasCallback);
+QMLDOM_FIELD(hasCustomParser);
+QMLDOM_FIELD(idStr);
+QMLDOM_FIELD(identifier);
+QMLDOM_FIELD(ids);
+QMLDOM_FIELD(implicit);
+QMLDOM_FIELD(import);
+QMLDOM_FIELD(importId);
+QMLDOM_FIELD(importScope);
+QMLDOM_FIELD(importSources);
+QMLDOM_FIELD(imported);
+QMLDOM_FIELD(imports);
+QMLDOM_FIELD(inProgress);
+QMLDOM_FIELD(infoItem);
+QMLDOM_FIELD(inheritVersion);
+QMLDOM_FIELD(initializer);
+QMLDOM_FIELD(interfaceNames);
+QMLDOM_FIELD(isAlias);
+QMLDOM_FIELD(isComposite);
+QMLDOM_FIELD(isConstructor);
+QMLDOM_FIELD(isCreatable);
+QMLDOM_FIELD(isDefaultMember);
+QMLDOM_FIELD(isFinal);
+QMLDOM_FIELD(isInternal);
+QMLDOM_FIELD(isLatest);
+QMLDOM_FIELD(isList);
+QMLDOM_FIELD(isPointer);
+QMLDOM_FIELD(isReadonly);
+QMLDOM_FIELD(isRequired);
+QMLDOM_FIELD(isSignalHandler);
+QMLDOM_FIELD(isSingleton);
+QMLDOM_FIELD(isValid);
+QMLDOM_FIELD(jsFileWithPath);
+QMLDOM_FIELD(kind);
+QMLDOM_FIELD(lastRevision);
+QMLDOM_FIELD(label);
+QMLDOM_FIELD(lastValidRevision);
+QMLDOM_FIELD(left);
+QMLDOM_FIELD(loadInfo);
+QMLDOM_FIELD(loadOptions);
+QMLDOM_FIELD(loadPaths);
+QMLDOM_FIELD(loadsWithWork);
+QMLDOM_FIELD(localOffset);
+QMLDOM_FIELD(location);
+QMLDOM_FIELD(logicalPath);
+QMLDOM_FIELD(majorVersion);
+QMLDOM_FIELD(metaRevisions);
+QMLDOM_FIELD(methodType);
+QMLDOM_FIELD(methods);
+QMLDOM_FIELD(minorVersion);
+QMLDOM_FIELD(moduleIndex);
+QMLDOM_FIELD(moduleIndexWithUri);
+QMLDOM_FIELD(moduleScope);
+QMLDOM_FIELD(moreCaseClauses);
+QMLDOM_FIELD(nAllLoadedCallbacks);
+QMLDOM_FIELD(nCallbacks);
+QMLDOM_FIELD(nLoaded);
+QMLDOM_FIELD(nNotdone);
+QMLDOM_FIELD(name);
+QMLDOM_FIELD(nameIdentifiers);
+QMLDOM_FIELD(newlinesBefore);
+QMLDOM_FIELD(nextComponent);
+QMLDOM_FIELD(nextScope);
+QMLDOM_FIELD(notify);
+QMLDOM_FIELD(objects);
+QMLDOM_FIELD(onAttachedObject);
+QMLDOM_FIELD(operation);
+QMLDOM_FIELD(options);
+QMLDOM_FIELD(parameters);
+QMLDOM_FIELD(parent);
+QMLDOM_FIELD(parentObject);
+QMLDOM_FIELD(path);
+QMLDOM_FIELD(plugins);
+QMLDOM_FIELD(postCode);
+QMLDOM_FIELD(postCommentLocations);
+QMLDOM_FIELD(postComments);
+QMLDOM_FIELD(pragma);
+QMLDOM_FIELD(pragmas);
+QMLDOM_FIELD(preCode);
+QMLDOM_FIELD(preCommentLocations);
+QMLDOM_FIELD(preComments);
+QMLDOM_FIELD(properties);
+QMLDOM_FIELD(propertyDef);
+QMLDOM_FIELD(propertyDefRef);
+QMLDOM_FIELD(propertyDefs);
+QMLDOM_FIELD(propertyInfos);
+QMLDOM_FIELD(propertyName);
+QMLDOM_FIELD(prototypes);
+QMLDOM_FIELD(qmlDirectoryWithPath);
+QMLDOM_FIELD(qmlFileWithPath);
+QMLDOM_FIELD(qmlFiles);
+QMLDOM_FIELD(qmldirFileWithPath);
+QMLDOM_FIELD(qmldirWithPath);
+QMLDOM_FIELD(qmltypesFileWithPath);
+QMLDOM_FIELD(qmltypesFiles);
+QMLDOM_FIELD(qualifiedImports);
+QMLDOM_FIELD(rawComment);
+QMLDOM_FIELD(read);
+QMLDOM_FIELD(referredObject);
+QMLDOM_FIELD(referredObjectPath);
+QMLDOM_FIELD(regionComments);
+QMLDOM_FIELD(regions);
+QMLDOM_FIELD(requestedAt);
+QMLDOM_FIELD(requestingUniverse);
+QMLDOM_FIELD(returnType);
+QMLDOM_FIELD(returnTypeName);
+QMLDOM_FIELD(right);
+QMLDOM_FIELD(rootComponent);
+QMLDOM_FIELD(scopeType);
+QMLDOM_FIELD(scriptElement);
+QMLDOM_FIELD(sources);
+QMLDOM_FIELD(statement);
+QMLDOM_FIELD(statements);
+QMLDOM_FIELD(status);
+QMLDOM_FIELD(stringValue);
+QMLDOM_FIELD(subComponents);
+QMLDOM_FIELD(subImports);
+QMLDOM_FIELD(subItems);
+QMLDOM_FIELD(symbol);
+QMLDOM_FIELD(symbols);
+QMLDOM_FIELD(target);
+QMLDOM_FIELD(targetPropertyName);
+QMLDOM_FIELD(text);
+QMLDOM_FIELD(type);
+QMLDOM_FIELD(typeArgument);
+QMLDOM_FIELD(typeArgumentName);
+QMLDOM_FIELD(typeName);
+QMLDOM_FIELD(types);
+QMLDOM_FIELD(universe);
+QMLDOM_FIELD(updatedScriptExpressions);
+QMLDOM_FIELD(uri);
+QMLDOM_FIELD(uris);
+QMLDOM_FIELD(validExposedAt);
+QMLDOM_FIELD(validItem);
+QMLDOM_FIELD(value);
+QMLDOM_FIELD(valueTypeName);
+QMLDOM_FIELD(values);
+QMLDOM_FIELD(version);
+QMLDOM_FIELD(when);
+QMLDOM_FIELD(write);
+} // namespace Fields
class Source;
size_t qHash(const Path &, size_t);
-
+class PathIterator;
// Define a iterator for it?
// begin() can basically be itself, end() the empty path (zero length), iteration though dropFront()
class QMLDOM_EXPORT Path{
+ Q_GADGET
Q_DECLARE_TR_FUNCTIONS(ErrorGroup);
public:
using Kind = PathEls::Kind;
using Component = PathEls::PathComponent;
static ErrorGroups myErrors(); // use static consts and central registration instead?
- Path(){}
+ Path() = default;
+ explicit Path(const PathEls::PathComponent &c) : m_endOffset(0), m_length(0)
+ {
+ *this = appendComponent(c);
+ }
+
int length() const { return m_length; }
Path operator[](int i) const;
- operator bool() const;
+ explicit operator bool() const;
+
+ PathIterator begin() const;
+ PathIterator end() const;
PathRoot headRoot() const;
PathCurrent headCurrent() const;
@@ -605,47 +617,49 @@ public:
QString headName() const;
bool checkHeadName(QStringView name) const;
index_type headIndex(index_type defaultValue=-1) const;
- std::function<bool(DomItem)> headFilter() const;
+ std::function<bool(const DomItem &)> headFilter() const;
Path head() const;
Path last() const;
Source split() const;
- void dump(Sink sink) const;
+ void dump(const Sink &sink) const;
QString toString() const;
- Path dropFront() const;
- Path dropTail() const;
+ Path dropFront(int n = 1) const;
+ Path dropTail(int n = 1) const;
Path mid(int offset, int length) const;
Path mid(int offset) const;
+ Path appendComponent(const PathEls::PathComponent &c);
// # Path construction
- static Path fromString(QString s, ErrorHandler errorHandler=nullptr);
- static Path fromString(QStringView s, ErrorHandler errorHandler=nullptr);
- static Path root(PathRoot r);
- static Path root(QStringView s=u"");
- static Path root(QString s);
- static Path index(index_type i);
- static Path field(QStringView s=u"");
- static Path field(QString s);
- static Path key(QStringView s=u"");
- static Path key(QString s);
- static Path current(PathCurrent c);
- static Path current(QStringView s=u"");
- static Path current(QString s);
- static Path empty();
+ static Path fromString(const QString &s, const ErrorHandler &errorHandler = nullptr);
+ static Path fromString(QStringView s, const ErrorHandler &errorHandler = nullptr);
+ static Path Root(PathRoot r);
+ static Path Root(QStringView s=u"");
+ static Path Root(const QString &s);
+ static Path Index(index_type i);
+ static Path Field(QStringView s=u"");
+ static Path Field(const QString &s);
+ static Path Key(QStringView s=u"");
+ static Path Key(const QString &s);
+ static Path Current(PathCurrent c);
+ static Path Current(QStringView s=u"");
+ static Path Current(const QString &s);
+ static Path Empty();
// add
- Path subEmpty() const;
- Path subField(QString name) const;
- Path subField(QStringView name) const;
- Path subKey(QString name) const;
- Path subKey(QStringView name) const;
- Path subIndex(index_type i) const;
- Path subAny() const;
- Path subFilter(std::function<bool(DomItem)>, QString) const;
- Path subFilter(std::function<bool(DomItem)>, QStringView desc=u"<native code filter>") const;
- Path subCurrent(PathCurrent s) const;
- Path subCurrent(QString s) const;
- Path subCurrent(QStringView s=u"") const;
- Path subPath(Path toAdd, bool avoidToAddAsBase = false) const;
+ Path empty() const;
+ Path field(const QString &name) const;
+ Path field(QStringView name) const;
+ Path key(const QString &name) const;
+ Path key(QStringView name) const;
+ Path index(index_type i) const;
+ Path any() const;
+ Path filter(const std::function<bool(const DomItem &)> &, const QString &) const;
+ Path filter(const std::function<bool(const DomItem &)> &,
+ QStringView desc=u"<native code filter>") const;
+ Path current(PathCurrent s) const;
+ Path current(const QString &s) const;
+ Path current(QStringView s=u"") const;
+ Path path(const Path &toAdd, bool avoidToAddAsBase = false) const;
Path expandFront() const;
Path expandBack() const;
@@ -661,25 +675,56 @@ public:
using iterator_category = std::forward_iterator_tag;
static int cmp(const Path &p1, const Path &p2);
+
private:
- explicit Path(quint16 endOffset, quint16 length, std::shared_ptr<PathEls::PathData> data);
+ const Component &component(int i) const;
+ explicit Path(quint16 endOffset, quint16 length,
+ const std::shared_ptr<PathEls::PathData> &data);
friend class QQmlJS::Dom::PathEls::TestPaths;
+ friend class FieldFilter;
friend size_t qHash(const Path &, size_t);
- Component component(int i) const;
Path noEndOffset() const;
quint16 m_endOffset = 0;
quint16 m_length = 0;
- std::shared_ptr<PathEls::PathData> m_data;
+ std::shared_ptr<PathEls::PathData> m_data = {};
};
-inline bool operator==(const Path& lhs, const Path& rhs){ return lhs.length() == rhs.length() && Path::cmp(lhs,rhs) == 0; }
-inline bool operator!=(const Path& lhs, const Path& rhs){ return lhs.length() != rhs.length() || Path::cmp(lhs,rhs) != 0; }
-inline bool operator< (const Path& lhs, const Path& rhs){ return Path::cmp(lhs,rhs) < 0; }
-inline bool operator> (const Path& lhs, const Path& rhs){ return Path::cmp(lhs,rhs) > 0; }
-inline bool operator<=(const Path& lhs, const Path& rhs){ return Path::cmp(lhs,rhs) <= 0; }
-inline bool operator>=(const Path& lhs, const Path& rhs){ return Path::cmp(lhs,rhs) >= 0; }
+inline bool operator==(const Path &lhs, const Path &rhs)
+{
+ return lhs.length() == rhs.length() && Path::cmp(lhs, rhs) == 0;
+}
+inline bool operator!=(const Path &lhs, const Path &rhs)
+{
+ return lhs.length() != rhs.length() || Path::cmp(lhs, rhs) != 0;
+}
+inline bool operator<(const Path &lhs, const Path &rhs)
+{
+ return Path::cmp(lhs, rhs) < 0;
+}
+inline bool operator>(const Path &lhs, const Path &rhs)
+{
+ return Path::cmp(lhs, rhs) > 0;
+}
+inline bool operator<=(const Path &lhs, const Path &rhs)
+{
+ return Path::cmp(lhs, rhs) <= 0;
+}
+inline bool operator>=(const Path &lhs, const Path &rhs)
+{
+ return Path::cmp(lhs, rhs) >= 0;
+}
+
+class PathIterator {
+public:
+ Path currentEl;
+ Path operator *() const { return currentEl.head(); }
+ PathIterator operator ++() { currentEl = currentEl.dropFront(); return *this; }
+ PathIterator operator ++(int) { PathIterator res{currentEl}; currentEl = currentEl.dropFront(); return res; }
+ bool operator ==(const PathIterator &o) const { return currentEl == o.currentEl; }
+ bool operator !=(const PathIterator &o) const { return currentEl != o.currentEl; }
+};
class Source {
public:
@@ -704,6 +749,9 @@ inline size_t qHash(const Path &path, size_t seed)
*it++ = qHash(p.component(0).stringView(), seed)^size_t(p.headRoot())^size_t(p.headCurrent());
}
}
+
+ // TODO: Get rid of the reinterpret_cast.
+ // Rather hash the path components in a more structured way.
return qHash(QByteArray::fromRawData(reinterpret_cast<char *>(&buf[0]), (it - &buf[0])*sizeof(size_t)), seed);
}
diff --git a/src/qmldom/qqmldomreformatter.cpp b/src/qmldom/qqmldomreformatter.cpp
new file mode 100644
index 0000000000..5001154027
--- /dev/null
+++ b/src/qmldom/qqmldomreformatter.cpp
@@ -0,0 +1,1136 @@
+// Copyright (C) 2021 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+
+#include "qqmldomreformatter_p.h"
+#include "qqmldomcomments_p.h"
+
+#include <QtQml/private/qqmljsast_p.h>
+#include <QtQml/private/qqmljsastvisitor_p.h>
+#include <QtQml/private/qqmljsengine_p.h>
+#include <QtQml/private/qqmljslexer_p.h>
+
+#include <QString>
+
+#include <algorithm>
+#include <limits>
+
+QT_BEGIN_NAMESPACE
+namespace QQmlJS {
+namespace Dom {
+
+using namespace AST;
+
+bool ScriptFormatter::preVisit(Node *n)
+{
+ if (CommentedElement *c = comments->commentForNode(n)) {
+ c->writePre(lw);
+ postOps[n].append([c, this]() { c->writePost(lw); });
+ }
+ return true;
+}
+void ScriptFormatter::postVisit(Node *n)
+{
+ for (auto &op : postOps[n]) {
+ op();
+ }
+ postOps.remove(n);
+}
+
+void ScriptFormatter::lnAcceptIndented(Node *node)
+{
+ int indent = lw.increaseIndent(1);
+ lw.ensureNewline();
+ accept(node);
+ lw.decreaseIndent(1, indent);
+}
+
+bool ScriptFormatter::acceptBlockOrIndented(Node *ast, bool finishWithSpaceOrNewline)
+{
+ if (cast<Block *>(ast)) {
+ out(" ");
+ accept(ast);
+ if (finishWithSpaceOrNewline)
+ out(" ");
+ return true;
+ } else {
+ if (finishWithSpaceOrNewline)
+ postOps[ast].append([this]() { this->newLine(); });
+ lnAcceptIndented(ast);
+ return false;
+ }
+}
+
+void ScriptFormatter::outputScope(VariableScope scope)
+{
+ switch (scope) {
+ case VariableScope::Const:
+ out("const ");
+ break;
+ case VariableScope::Let:
+ out("let ");
+ break;
+ case VariableScope::Var:
+ out("var ");
+ break;
+ default:
+ break;
+ }
+}
+
+bool ScriptFormatter::visit(ThisExpression *ast)
+{
+ out(ast->thisToken);
+ return true;
+}
+
+bool ScriptFormatter::visit(NullExpression *ast)
+{
+ out(ast->nullToken);
+ return true;
+}
+bool ScriptFormatter::visit(TrueLiteral *ast)
+{
+ out(ast->trueToken);
+ return true;
+}
+bool ScriptFormatter::visit(FalseLiteral *ast)
+{
+ out(ast->falseToken);
+ return true;
+}
+
+bool ScriptFormatter::visit(IdentifierExpression *ast)
+{
+ out(ast->identifierToken);
+ return true;
+}
+bool ScriptFormatter::visit(StringLiteral *ast)
+{
+ // correctly handle multiline literals
+ if (ast->literalToken.length == 0)
+ return true;
+ QStringView str = loc2Str(ast->literalToken);
+ if (lw.indentNextlines && str.contains(QLatin1Char('\n'))) {
+ out(str.mid(0, 1));
+ lw.indentNextlines = false;
+ out(str.mid(1));
+ lw.indentNextlines = true;
+ } else {
+ out(str);
+ }
+ return true;
+}
+bool ScriptFormatter::visit(NumericLiteral *ast)
+{
+ out(ast->literalToken);
+ return true;
+}
+bool ScriptFormatter::visit(RegExpLiteral *ast)
+{
+ out(ast->literalToken);
+ return true;
+}
+
+bool ScriptFormatter::visit(ArrayPattern *ast)
+{
+ out(ast->lbracketToken);
+ int baseIndent = lw.increaseIndent(1);
+ if (ast->elements) {
+ accept(ast->elements);
+ out(ast->commaToken);
+ auto lastElement = lastListElement(ast->elements);
+ if (lastElement->element && cast<ObjectPattern *>(lastElement->element->initializer)) {
+ newLine();
+ }
+ } else {
+ out(ast->commaToken);
+ }
+ lw.decreaseIndent(1, baseIndent);
+ out(ast->rbracketToken);
+ return false;
+}
+
+bool ScriptFormatter::visit(ObjectPattern *ast)
+{
+ out(ast->lbraceToken);
+ ++expressionDepth;
+ if (ast->properties) {
+ lnAcceptIndented(ast->properties);
+ newLine();
+ }
+ --expressionDepth;
+ out(ast->rbraceToken);
+ return false;
+}
+
+bool ScriptFormatter::visit(PatternElementList *ast)
+{
+ for (PatternElementList *it = ast; it; it = it->next) {
+ const bool isObjectInitializer =
+ it->element && cast<ObjectPattern *>(it->element->initializer);
+ if (isObjectInitializer)
+ newLine();
+
+ if (it->elision)
+ accept(it->elision);
+ if (it->elision && it->element)
+ out(", ");
+ if (it->element)
+ accept(it->element);
+ if (it->next) {
+ out(", ");
+ if (isObjectInitializer)
+ newLine();
+ }
+ }
+ return false;
+}
+
+bool ScriptFormatter::visit(PatternPropertyList *ast)
+{
+ for (PatternPropertyList *it = ast; it; it = it->next) {
+ accept(it->property);
+ if (it->next) {
+ out(",");
+ newLine();
+ }
+ }
+ return false;
+}
+
+// https://262.ecma-international.org/7.0/#prod-PropertyDefinition
+bool ScriptFormatter::visit(AST::PatternProperty *property)
+{
+ if (property->type == PatternElement::Getter || property->type == PatternElement::Setter
+ || property->type == PatternElement::Method) {
+ // note that MethodDefinitions and FunctionDeclarations have different syntax
+ // https://262.ecma-international.org/7.0/#prod-MethodDefinition
+ // https://262.ecma-international.org/7.0/#prod-FunctionDeclaration
+ // hence visit(FunctionDeclaration*) is not quite appropriate here
+ if (property->type == PatternProperty::Getter)
+ out("get ");
+ else if (property->type == PatternProperty::Setter)
+ out("set ");
+ FunctionExpression *f = AST::cast<FunctionExpression *>(property->initializer);
+ if (f->isGenerator) {
+ out("*");
+ }
+ accept(property->name);
+ out(f->lparenToken);
+ accept(f->formals);
+ out(f->rparenToken);
+ out(f->lbraceToken);
+ const bool scoped = f->lbraceToken.isValid();
+ if (scoped)
+ ++expressionDepth;
+ if (f->body) {
+ if (f->body->next || scoped) {
+ lnAcceptIndented(f->body);
+ lw.newline();
+ } else {
+ auto baseIndent = lw.increaseIndent(1);
+ accept(f->body);
+ lw.decreaseIndent(1, baseIndent);
+ }
+ }
+ if (scoped)
+ --expressionDepth;
+ out(f->rbraceToken);
+ return false;
+ }
+
+ // IdentifierReference[?Yield]
+ accept(property->name);
+ bool useInitializer = false;
+ const bool bindingIdentifierExist = !property->bindingIdentifier.isEmpty();
+ if (property->colonToken.isValid()) {
+ // PropertyName[?Yield] : AssignmentExpression[In, ?Yield]
+ out(": ");
+ useInitializer = true;
+ if (bindingIdentifierExist)
+ out(property->bindingIdentifier);
+ if (property->bindingTarget)
+ accept(property->bindingTarget);
+ }
+
+ if (property->initializer) {
+ // CoverInitializedName[?Yield]
+ if (bindingIdentifierExist) {
+ out(" = ");
+ useInitializer = true;
+ }
+ if (useInitializer)
+ accept(property->initializer);
+ }
+ return false;
+}
+
+bool ScriptFormatter::visit(NestedExpression *ast)
+{
+ out(ast->lparenToken);
+ int baseIndent = lw.increaseIndent(1);
+ accept(ast->expression);
+ lw.decreaseIndent(1, baseIndent);
+ out(ast->rparenToken);
+ return false;
+}
+
+bool ScriptFormatter::visit(IdentifierPropertyName *ast)
+{
+ out(ast->id.toString());
+ return true;
+}
+bool ScriptFormatter::visit(StringLiteralPropertyName *ast)
+{
+ out(ast->propertyNameToken);
+ return true;
+}
+bool ScriptFormatter::visit(NumericLiteralPropertyName *ast)
+{
+ out(QString::number(ast->id));
+ return true;
+}
+
+bool ScriptFormatter::visit(TemplateLiteral *ast)
+{
+ // correctly handle multiline literals
+ if (ast->literalToken.length != 0) {
+ QStringView str = loc2Str(ast->literalToken);
+ if (lw.indentNextlines && str.contains(QLatin1Char('\n'))) {
+ out(str.mid(0, 1));
+ lw.indentNextlines = false;
+ out(str.mid(1));
+ lw.indentNextlines = true;
+ } else {
+ out(str);
+ }
+ }
+ accept(ast->expression);
+ return true;
+}
+
+bool ScriptFormatter::visit(ArrayMemberExpression *ast)
+{
+ accept(ast->base);
+ out(ast->lbracketToken);
+ int indent = lw.increaseIndent(1);
+ accept(ast->expression);
+ lw.decreaseIndent(1, indent);
+ out(ast->rbracketToken);
+ return false;
+}
+
+bool ScriptFormatter::visit(FieldMemberExpression *ast)
+{
+ accept(ast->base);
+ out(ast->dotToken);
+ out(ast->identifierToken);
+ return false;
+}
+
+bool ScriptFormatter::visit(NewMemberExpression *ast)
+{
+ out("new "); // ast->newToken
+ accept(ast->base);
+ out(ast->lparenToken);
+ accept(ast->arguments);
+ out(ast->rparenToken);
+ return false;
+}
+
+bool ScriptFormatter::visit(NewExpression *ast)
+{
+ out("new "); // ast->newToken
+ accept(ast->expression);
+ return false;
+}
+
+bool ScriptFormatter::visit(CallExpression *ast)
+{
+ accept(ast->base);
+ out(ast->lparenToken);
+ accept(ast->arguments);
+ out(ast->rparenToken);
+ return false;
+}
+
+bool ScriptFormatter::visit(PostIncrementExpression *ast)
+{
+ accept(ast->base);
+ out(ast->incrementToken);
+ return false;
+}
+
+bool ScriptFormatter::visit(PostDecrementExpression *ast)
+{
+ accept(ast->base);
+ out(ast->decrementToken);
+ return false;
+}
+
+bool ScriptFormatter::visit(PreIncrementExpression *ast)
+{
+ out(ast->incrementToken);
+ accept(ast->expression);
+ return false;
+}
+
+bool ScriptFormatter::visit(PreDecrementExpression *ast)
+{
+ out(ast->decrementToken);
+ accept(ast->expression);
+ return false;
+}
+
+bool ScriptFormatter::visit(DeleteExpression *ast)
+{
+ out("delete "); // ast->deleteToken
+ accept(ast->expression);
+ return false;
+}
+
+bool ScriptFormatter::visit(VoidExpression *ast)
+{
+ out("void "); // ast->voidToken
+ accept(ast->expression);
+ return false;
+}
+
+bool ScriptFormatter::visit(TypeOfExpression *ast)
+{
+ out("typeof "); // ast->typeofToken
+ accept(ast->expression);
+ return false;
+}
+
+bool ScriptFormatter::visit(UnaryPlusExpression *ast)
+{
+ out(ast->plusToken);
+ accept(ast->expression);
+ return false;
+}
+
+bool ScriptFormatter::visit(UnaryMinusExpression *ast)
+{
+ out(ast->minusToken);
+ accept(ast->expression);
+ return false;
+}
+
+bool ScriptFormatter::visit(TildeExpression *ast)
+{
+ out(ast->tildeToken);
+ accept(ast->expression);
+ return false;
+}
+
+bool ScriptFormatter::visit(NotExpression *ast)
+{
+ out(ast->notToken);
+ accept(ast->expression);
+ return false;
+}
+
+bool ScriptFormatter::visit(BinaryExpression *ast)
+{
+ accept(ast->left);
+ out(" ");
+ out(ast->operatorToken);
+ out(" ");
+ accept(ast->right);
+ return false;
+}
+
+bool ScriptFormatter::visit(ConditionalExpression *ast)
+{
+ accept(ast->expression);
+ out(" ? "); // ast->questionToken
+ accept(ast->ok);
+ out(" : "); // ast->colonToken
+ accept(ast->ko);
+ return false;
+}
+
+bool ScriptFormatter::visit(Block *ast)
+{
+ out(ast->lbraceToken);
+ if (ast->statements) {
+ ++expressionDepth;
+ lnAcceptIndented(ast->statements);
+ newLine();
+ --expressionDepth;
+ }
+ out(ast->rbraceToken);
+ return false;
+}
+
+bool ScriptFormatter::visit(VariableStatement *ast)
+{
+ out(ast->declarationKindToken);
+ out(" ");
+ accept(ast->declarations);
+ if (addSemicolons())
+ out(";");
+ return false;
+}
+
+bool ScriptFormatter::visit(PatternElement *ast)
+{
+ if (ast->isForDeclaration) {
+ outputScope(ast->scope);
+ }
+ switch (ast->type) {
+ case PatternElement::Literal:
+ case PatternElement::Method:
+ case PatternElement::Binding:
+ break;
+ case PatternElement::Getter:
+ out("get ");
+ break;
+ case PatternElement::Setter:
+ out("set ");
+ break;
+ case PatternElement::SpreadElement:
+ out("...");
+ break;
+ }
+
+ accept(ast->bindingTarget);
+ if (!ast->destructuringPattern())
+ out(ast->identifierToken);
+ if (ast->initializer) {
+ if (ast->isVariableDeclaration() || ast->type == AST::PatternElement::Binding)
+ out(" = ");
+ accept(ast->initializer);
+ }
+ return false;
+}
+
+bool ScriptFormatter::visit(EmptyStatement *ast)
+{
+ out(ast->semicolonToken);
+ return false;
+}
+
+bool ScriptFormatter::visit(IfStatement *ast)
+{
+ out(ast->ifToken);
+ out(" ");
+ out(ast->lparenToken);
+ preVisit(ast->expression);
+ ast->expression->accept0(this);
+ out(ast->rparenToken);
+ postVisit(ast->expression);
+ acceptBlockOrIndented(ast->ok, ast->ko);
+ if (ast->ko) {
+ out(ast->elseToken);
+ if (cast<Block *>(ast->ko) || cast<IfStatement *>(ast->ko)) {
+ out(" ");
+ accept(ast->ko);
+ } else {
+ lnAcceptIndented(ast->ko);
+ }
+ }
+ return false;
+}
+
+bool ScriptFormatter::visit(DoWhileStatement *ast)
+{
+ out(ast->doToken);
+ acceptBlockOrIndented(ast->statement, true);
+ out(ast->whileToken);
+ out(" ");
+ out(ast->lparenToken);
+ accept(ast->expression);
+ out(ast->rparenToken);
+ return false;
+}
+
+bool ScriptFormatter::visit(WhileStatement *ast)
+{
+ out(ast->whileToken);
+ out(" ");
+ out(ast->lparenToken);
+ accept(ast->expression);
+ out(ast->rparenToken);
+ acceptBlockOrIndented(ast->statement);
+ return false;
+}
+
+bool ScriptFormatter::visit(ForStatement *ast)
+{
+ out(ast->forToken);
+ out(" ");
+ out(ast->lparenToken);
+ if (ast->initialiser) {
+ accept(ast->initialiser);
+ } else if (ast->declarations) {
+ outputScope(ast->declarations->declaration->scope);
+ accept(ast->declarations);
+ }
+ out("; "); // ast->firstSemicolonToken
+ accept(ast->condition);
+ out("; "); // ast->secondSemicolonToken
+ accept(ast->expression);
+ out(ast->rparenToken);
+ acceptBlockOrIndented(ast->statement);
+ return false;
+}
+
+bool ScriptFormatter::visit(ForEachStatement *ast)
+{
+ out(ast->forToken);
+ out(" ");
+ out(ast->lparenToken);
+ accept(ast->lhs);
+ out(" ");
+ out(ast->inOfToken);
+ out(" ");
+ accept(ast->expression);
+ out(ast->rparenToken);
+ acceptBlockOrIndented(ast->statement);
+ return false;
+}
+
+bool ScriptFormatter::visit(ContinueStatement *ast)
+{
+ out(ast->continueToken);
+ if (!ast->label.isNull()) {
+ out(" ");
+ out(ast->identifierToken);
+ }
+ if (addSemicolons())
+ out(";");
+ return false;
+}
+
+bool ScriptFormatter::visit(BreakStatement *ast)
+{
+ out(ast->breakToken);
+ if (!ast->label.isNull()) {
+ out(" ");
+ out(ast->identifierToken);
+ }
+ if (addSemicolons())
+ out(";");
+ return false;
+}
+
+bool ScriptFormatter::visit(ReturnStatement *ast)
+{
+ out(ast->returnToken);
+ if (ast->expression) {
+ if (ast->returnToken.length != 0)
+ out(" ");
+ accept(ast->expression);
+ }
+ if (ast->returnToken.length > 0 && addSemicolons())
+ out(";");
+ return false;
+}
+
+bool ScriptFormatter::visit(ThrowStatement *ast)
+{
+ out(ast->throwToken);
+ if (ast->expression) {
+ out(" ");
+ accept(ast->expression);
+ }
+ if (addSemicolons())
+ out(";");
+ return false;
+}
+
+bool ScriptFormatter::visit(WithStatement *ast)
+{
+ out(ast->withToken);
+ out(" ");
+ out(ast->lparenToken);
+ accept(ast->expression);
+ out(ast->rparenToken);
+ acceptBlockOrIndented(ast->statement);
+ return false;
+}
+
+bool ScriptFormatter::visit(SwitchStatement *ast)
+{
+ out(ast->switchToken);
+ out(" ");
+ out(ast->lparenToken);
+ accept(ast->expression);
+ out(ast->rparenToken);
+ out(" ");
+ accept(ast->block);
+ return false;
+}
+
+bool ScriptFormatter::visit(CaseBlock *ast)
+{
+ out(ast->lbraceToken);
+ ++expressionDepth;
+ newLine();
+ accept(ast->clauses);
+ if (ast->clauses && ast->defaultClause)
+ newLine();
+ accept(ast->defaultClause);
+ if (ast->moreClauses)
+ newLine();
+ accept(ast->moreClauses);
+ newLine();
+ --expressionDepth;
+ out(ast->rbraceToken);
+ return false;
+}
+
+bool ScriptFormatter::visit(CaseClause *ast)
+{
+ out("case "); // ast->caseToken
+ accept(ast->expression);
+ out(ast->colonToken);
+ if (ast->statements)
+ lnAcceptIndented(ast->statements);
+ return false;
+}
+
+bool ScriptFormatter::visit(DefaultClause *ast)
+{
+ out(ast->defaultToken);
+ out(ast->colonToken);
+ lnAcceptIndented(ast->statements);
+ return false;
+}
+
+bool ScriptFormatter::visit(LabelledStatement *ast)
+{
+ out(ast->identifierToken);
+ out(": "); // ast->colonToken
+ accept(ast->statement);
+ return false;
+}
+
+bool ScriptFormatter::visit(TryStatement *ast)
+{
+ out("try "); // ast->tryToken
+ accept(ast->statement);
+ if (ast->catchExpression) {
+ out(" ");
+ accept(ast->catchExpression);
+ }
+ if (ast->finallyExpression) {
+ out(" ");
+ accept(ast->finallyExpression);
+ }
+ return false;
+}
+
+bool ScriptFormatter::visit(Catch *ast)
+{
+ out(ast->catchToken);
+ out(" ");
+ out(ast->lparenToken);
+ out(ast->identifierToken);
+ out(") "); // ast->rparenToken
+ accept(ast->statement);
+ return false;
+}
+
+bool ScriptFormatter::visit(Finally *ast)
+{
+ out("finally "); // ast->finallyToken
+ accept(ast->statement);
+ return false;
+}
+
+bool ScriptFormatter::visit(FunctionDeclaration *ast)
+{
+ return ScriptFormatter::visit(static_cast<FunctionExpression *>(ast));
+}
+
+bool ScriptFormatter::visit(FunctionExpression *ast)
+{
+ if (!ast->isArrowFunction) {
+ if (ast->isGenerator) {
+ out("function* ");
+ } else {
+ out("function ");
+ }
+ if (!ast->name.isNull())
+ out(ast->identifierToken);
+ }
+ out(ast->lparenToken);
+ const bool needParentheses = ast->formals
+ && (ast->formals->next
+ || (ast->formals->element && ast->formals->element->bindingTarget));
+ if (ast->isArrowFunction && needParentheses)
+ out("(");
+ int baseIndent = lw.increaseIndent(1);
+ accept(ast->formals);
+ lw.decreaseIndent(1, baseIndent);
+ if (ast->isArrowFunction && needParentheses)
+ out(")");
+ out(ast->rparenToken);
+ if (ast->isArrowFunction && !ast->formals)
+ out("()");
+ out(" ");
+ if (ast->isArrowFunction)
+ out("=> ");
+ out(ast->lbraceToken);
+ if (ast->lbraceToken.length != 0)
+ ++expressionDepth;
+ if (ast->body) {
+ if (ast->body->next || ast->lbraceToken.length != 0) {
+ lnAcceptIndented(ast->body);
+ newLine();
+ } else {
+ // print a single statement in one line. E.g. x => x * 2
+ baseIndent = lw.increaseIndent(1);
+ accept(ast->body);
+ lw.decreaseIndent(1, baseIndent);
+ }
+ }
+ if (ast->lbraceToken.length != 0)
+ --expressionDepth;
+ out(ast->rbraceToken);
+ return false;
+}
+
+bool ScriptFormatter::visit(Elision *ast)
+{
+ for (Elision *it = ast; it; it = it->next) {
+ if (it->next)
+ out(", "); // ast->commaToken
+ }
+ return false;
+}
+
+bool ScriptFormatter::visit(ArgumentList *ast)
+{
+ for (ArgumentList *it = ast; it; it = it->next) {
+ if (it->isSpreadElement)
+ out("...");
+ accept(it->expression);
+ if (it->next) {
+ out(", "); // it->commaToken
+ }
+ }
+ return false;
+}
+
+bool ScriptFormatter::visit(StatementList *ast)
+{
+ ++expressionDepth;
+ for (StatementList *it = ast; it; it = it->next) {
+ // ### work around parser bug: skip empty statements with wrong tokens
+ if (EmptyStatement *emptyStatement = cast<EmptyStatement *>(it->statement)) {
+ if (loc2Str(emptyStatement->semicolonToken) != QLatin1String(";"))
+ continue;
+ }
+
+ accept(it->statement);
+ if (it->next) {
+ // There might be a post-comment attached to the current
+ // statement or a pre-comment attached to the next
+ // statmente or both.
+ // If any of those are present they will take care of
+ // handling the spacing between the statements so we
+ // don't need to push any newline.
+ auto *commentForCurrentStatement = comments->commentForNode(it->statement);
+ auto *commentForNextStatement = comments->commentForNode(it->next->statement);
+
+ if (
+ (commentForCurrentStatement && !commentForCurrentStatement->postComments().empty())
+ || (commentForNextStatement && !commentForNextStatement->preComments().empty())
+ ) continue;
+
+ quint32 lineDelta = it->next->firstSourceLocation().startLine
+ - it->statement->lastSourceLocation().startLine;
+ lineDelta = std::clamp(lineDelta, quint32{ 1 }, quint32{ 2 });
+
+ newLine(lineDelta);
+ }
+ }
+ --expressionDepth;
+ return false;
+}
+
+bool ScriptFormatter::visit(VariableDeclarationList *ast)
+{
+ for (VariableDeclarationList *it = ast; it; it = it->next) {
+ accept(it->declaration);
+ if (it->next)
+ out(", "); // it->commaToken
+ }
+ return false;
+}
+
+bool ScriptFormatter::visit(CaseClauses *ast)
+{
+ for (CaseClauses *it = ast; it; it = it->next) {
+ accept(it->clause);
+ if (it->next)
+ newLine();
+ }
+ return false;
+}
+
+bool ScriptFormatter::visit(FormalParameterList *ast)
+{
+ for (FormalParameterList *it = ast; it; it = it->next) {
+ // compare FormalParameterList::finish
+ if (auto id = it->element->bindingIdentifier.toString(); !id.isEmpty())
+ out(id);
+ if (it->element->bindingTarget)
+ accept(it->element->bindingTarget);
+ if (it->next)
+ out(", ");
+ }
+ return false;
+}
+
+// to check
+bool ScriptFormatter::visit(SuperLiteral *)
+{
+ out("super");
+ return true;
+}
+bool ScriptFormatter::visit(ComputedPropertyName *)
+{
+ out("[");
+ return true;
+}
+bool ScriptFormatter::visit(Expression *el)
+{
+ accept(el->left);
+ out(", ");
+ accept(el->right);
+ return false;
+}
+bool ScriptFormatter::visit(ExpressionStatement *el)
+{
+ if (addSemicolons())
+ postOps[el->expression].append([this]() { out(";"); });
+ return true;
+}
+
+// Return false because we want to omit default function calls in accept0 implementation.
+bool ScriptFormatter::visit(ClassDeclaration *ast)
+{
+ preVisit(ast);
+ out(ast->classToken);
+ out(" ");
+ out(ast->name);
+ if (ast->heritage) {
+ out(" extends ");
+ accept(ast->heritage);
+ }
+ out(" {");
+ int baseIndent = lw.increaseIndent();
+ for (ClassElementList *it = ast->elements; it; it = it->next) {
+ lw.newline();
+ if (it->isStatic)
+ out("static ");
+ accept(it->property);
+ lw.newline();
+ }
+ lw.decreaseIndent(1, baseIndent);
+ out("}");
+ postVisit(ast);
+ return false;
+}
+
+bool ScriptFormatter::visit(AST::ImportDeclaration *ast)
+{
+ out(ast->importToken);
+ lw.space();
+ if (!ast->moduleSpecifier.isNull()) {
+ out(ast->moduleSpecifierToken);
+ }
+ return true;
+}
+
+bool ScriptFormatter::visit(AST::ImportSpecifier *ast)
+{
+ if (!ast->identifier.isNull()) {
+ out(ast->identifierToken);
+ lw.space();
+ out("as");
+ lw.space();
+ }
+ out(ast->importedBindingToken);
+ return true;
+}
+
+bool ScriptFormatter::visit(AST::NameSpaceImport *ast)
+{
+ out(ast->starToken);
+ lw.space();
+ out("as");
+ lw.space();
+ out(ast->importedBindingToken);
+ return true;
+}
+
+bool ScriptFormatter::visit(AST::ImportsList *ast)
+{
+ for (ImportsList *it = ast; it; it = it->next) {
+ accept(it->importSpecifier);
+ if (it->next) {
+ out(",");
+ lw.space();
+ }
+ }
+ return false;
+}
+bool ScriptFormatter::visit(AST::NamedImports *ast)
+{
+ out(ast->leftBraceToken);
+ if (ast->importsList) {
+ lw.space();
+ }
+ return true;
+}
+
+bool ScriptFormatter::visit(AST::ImportClause *ast)
+{
+ if (!ast->importedDefaultBinding.isNull()) {
+ out(ast->importedDefaultBindingToken);
+ if (ast->nameSpaceImport || ast->namedImports) {
+ out(",");
+ lw.space();
+ }
+ }
+ return true;
+}
+
+bool ScriptFormatter::visit(AST::ExportDeclaration *ast)
+{
+ out(ast->exportToken);
+ lw.space();
+ if (ast->exportDefault) {
+ out("default");
+ lw.space();
+ }
+ if (ast->exportsAll()) {
+ out("*");
+ }
+ return true;
+}
+
+bool ScriptFormatter::visit(AST::ExportClause *ast)
+{
+ out(ast->leftBraceToken);
+ if (ast->exportsList) {
+ lw.space();
+ }
+ return true;
+}
+
+bool ScriptFormatter::visit(AST::ExportSpecifier *ast)
+{
+ out(ast->identifier);
+ if (ast->exportedIdentifierToken.isValid()) {
+ lw.space();
+ out("as");
+ lw.space();
+ out(ast->exportedIdentifier);
+ }
+ return true;
+}
+
+bool ScriptFormatter::visit(AST::ExportsList *ast)
+{
+ for (ExportsList *it = ast; it; it = it->next) {
+ accept(it->exportSpecifier);
+ if (it->next) {
+ out(",");
+ lw.space();
+ }
+ }
+ return false;
+}
+
+bool ScriptFormatter::visit(AST::FromClause *ast)
+{
+ lw.space();
+ out(ast->fromToken);
+ lw.space();
+ out(ast->moduleSpecifierToken);
+ return true;
+}
+
+void ScriptFormatter::endVisit(ComputedPropertyName *)
+{
+ out("]");
+}
+
+void ScriptFormatter::endVisit(AST::ExportDeclaration *ast)
+{
+ // add a semicolon at the end of the following expressions
+ // export * FromClause ;
+ // export ExportClause FromClause ;
+ if (ast->fromClause) {
+ out(";");
+ }
+
+ // add a semicolon at the end of the following expressions
+ // export ExportClause ;
+ if (ast->exportClause && !ast->fromClause) {
+ out(";");
+ }
+
+ // add a semicolon at the end of the following expressions
+ // export default [lookahead ∉ { function, class }] AssignmentExpression;
+ if (ast->exportDefault && ast->variableStatementOrDeclaration) {
+ // lookahead ∉ { function, class }
+ if (!(ast->variableStatementOrDeclaration->kind == Node::Kind_FunctionDeclaration
+ || ast->variableStatementOrDeclaration->kind == Node::Kind_ClassDeclaration)) {
+ out(";");
+ }
+ // ArrowFunction in QQmlJS::AST is handled with the help of FunctionDeclaration
+ // and not as part of AssignmentExpression (as per ECMA
+ // https://262.ecma-international.org/7.0/#prod-AssignmentExpression)
+ if (ast->variableStatementOrDeclaration->kind == Node::Kind_FunctionDeclaration
+ && static_cast<AST::FunctionDeclaration *>(ast->variableStatementOrDeclaration)
+ ->isArrowFunction) {
+ out(";");
+ }
+ }
+}
+
+void ScriptFormatter::endVisit(AST::ExportClause *ast)
+{
+ if (ast->exportsList) {
+ lw.space();
+ }
+ out(ast->rightBraceToken);
+}
+
+void ScriptFormatter::endVisit(AST::NamedImports *ast)
+{
+ if (ast->importsList) {
+ lw.space();
+ }
+ out(ast->rightBraceToken);
+}
+
+void ScriptFormatter::endVisit(AST::ImportDeclaration *)
+{
+ out(";");
+}
+
+void ScriptFormatter::throwRecursionDepthError()
+{
+ out("/* ERROR: Hit recursion limit ScriptFormatter::visiting AST, rewrite failed */");
+}
+
+void reformatAst(OutWriter &lw, const std::shared_ptr<AstComments> &comments,
+ const std::function<QStringView(SourceLocation)> &loc2Str, AST::Node *n)
+{
+ if (n) {
+ ScriptFormatter formatter(lw, comments, loc2Str, n);
+ }
+}
+
+} // namespace Dom
+} // namespace QQmlJS
+QT_END_NAMESPACE
diff --git a/src/qmldom/qqmldomreformatter_p.h b/src/qmldom/qqmldomreformatter_p.h
new file mode 100644
index 0000000000..565d021822
--- /dev/null
+++ b/src/qmldom/qqmldomreformatter_p.h
@@ -0,0 +1,223 @@
+// Copyright (C) 2021 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+
+#ifndef QQMLDOMREFORMATTER_P
+#define QQMLDOMREFORMATTER_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 "qqmldomoutwriter_p.h"
+#include "qqmldom_fwd_p.h"
+#include "qqmldomcomments_p.h"
+
+#include <QtQml/private/qqmljsast_p.h>
+
+QT_BEGIN_NAMESPACE
+namespace QQmlJS {
+namespace Dom {
+
+class ScriptFormatter final : protected AST::JSVisitor
+{
+public:
+ // TODO QTBUG-121988
+ ScriptFormatter(OutWriter &lw, const std::shared_ptr<AstComments> &comments,
+ const std::function<QStringView(SourceLocation)> &loc2Str, AST::Node *node)
+ : lw(lw), comments(comments), loc2Str(loc2Str)
+ {
+ accept(node);
+ }
+
+protected:
+ inline void out(const char *str) { lw.write(QString::fromLatin1(str)); }
+ inline void out(QStringView str) { lw.write(str); }
+ inline void out(const SourceLocation &loc)
+ {
+ if (loc.length != 0)
+ out(loc2Str(loc));
+ }
+ inline void newLine(quint32 count = 1) { lw.ensureNewline(count); }
+
+ inline void accept(AST::Node *node) { AST::Node::accept(node, this); }
+ void lnAcceptIndented(AST::Node *node);
+ bool acceptBlockOrIndented(AST::Node *ast, bool finishWithSpaceOrNewline = false);
+
+ void outputScope(AST::VariableScope scope);
+
+ bool preVisit(AST::Node *n) override;
+ void postVisit(AST::Node *n) override;
+
+ bool visit(AST::ThisExpression *ast) override;
+ bool visit(AST::NullExpression *ast) override;
+ bool visit(AST::TrueLiteral *ast) override;
+ bool visit(AST::FalseLiteral *ast) override;
+ bool visit(AST::IdentifierExpression *ast) override;
+ bool visit(AST::StringLiteral *ast) override;
+ bool visit(AST::NumericLiteral *ast) override;
+ bool visit(AST::RegExpLiteral *ast) override;
+
+ bool visit(AST::ArrayPattern *ast) override;
+
+ bool visit(AST::ObjectPattern *ast) override;
+
+ bool visit(AST::PatternElementList *ast) override;
+
+ bool visit(AST::PatternPropertyList *ast) override;
+ bool visit(AST::PatternProperty *property) override;
+
+ bool visit(AST::NestedExpression *ast) override;
+ bool visit(AST::IdentifierPropertyName *ast) override;
+ bool visit(AST::StringLiteralPropertyName *ast) override;
+ bool visit(AST::NumericLiteralPropertyName *ast) override;
+
+ bool visit(AST::TemplateLiteral *ast) override;
+ bool visit(AST::ArrayMemberExpression *ast) override;
+
+ bool visit(AST::FieldMemberExpression *ast) override;
+
+ bool visit(AST::NewMemberExpression *ast) override;
+
+ bool visit(AST::NewExpression *ast) override;
+
+ bool visit(AST::CallExpression *ast) override;
+
+ bool visit(AST::PostIncrementExpression *ast) override;
+
+ bool visit(AST::PostDecrementExpression *ast) override;
+ bool visit(AST::PreIncrementExpression *ast) override;
+
+ bool visit(AST::PreDecrementExpression *ast) override;
+
+ bool visit(AST::DeleteExpression *ast) override;
+
+ bool visit(AST::VoidExpression *ast) override;
+ bool visit(AST::TypeOfExpression *ast) override;
+
+ bool visit(AST::UnaryPlusExpression *ast) override;
+
+ bool visit(AST::UnaryMinusExpression *ast) override;
+
+ bool visit(AST::TildeExpression *ast) override;
+
+ bool visit(AST::NotExpression *ast) override;
+
+ bool visit(AST::BinaryExpression *ast) override;
+
+ bool visit(AST::ConditionalExpression *ast) override;
+
+ bool visit(AST::Block *ast) override;
+
+ bool visit(AST::VariableStatement *ast) override;
+
+ bool visit(AST::PatternElement *ast) override;
+
+ bool visit(AST::EmptyStatement *ast) override;
+
+ bool visit(AST::IfStatement *ast) override;
+ bool visit(AST::DoWhileStatement *ast) override;
+
+ bool visit(AST::WhileStatement *ast) override;
+
+ bool visit(AST::ForStatement *ast) override;
+
+ bool visit(AST::ForEachStatement *ast) override;
+
+ bool visit(AST::ContinueStatement *ast) override;
+ bool visit(AST::BreakStatement *ast) override;
+
+ bool visit(AST::ReturnStatement *ast) override;
+ bool visit(AST::ThrowStatement *ast) override;
+ bool visit(AST::WithStatement *ast) override;
+
+ bool visit(AST::SwitchStatement *ast) override;
+
+ bool visit(AST::CaseBlock *ast) override;
+
+ bool visit(AST::CaseClause *ast) override;
+
+ bool visit(AST::DefaultClause *ast) override;
+
+ bool visit(AST::LabelledStatement *ast) override;
+
+ bool visit(AST::TryStatement *ast) override;
+
+ bool visit(AST::Catch *ast) override;
+
+ bool visit(AST::Finally *ast) override;
+
+ bool visit(AST::FunctionDeclaration *ast) override;
+
+ bool visit(AST::FunctionExpression *ast) override;
+
+ bool visit(AST::Elision *ast) override;
+
+ bool visit(AST::ArgumentList *ast) override;
+
+ bool visit(AST::StatementList *ast) override;
+
+ bool visit(AST::VariableDeclarationList *ast) override;
+
+ bool visit(AST::CaseClauses *ast) override;
+
+ bool visit(AST::FormalParameterList *ast) override;
+
+ bool visit(AST::SuperLiteral *) override;
+ bool visit(AST::ComputedPropertyName *) override;
+ bool visit(AST::Expression *el) override;
+ bool visit(AST::ExpressionStatement *el) override;
+
+ bool visit(AST::ClassDeclaration *ast) override;
+
+ bool visit(AST::ImportDeclaration *ast) override;
+ bool visit(AST::ImportSpecifier *ast) override;
+ bool visit(AST::NameSpaceImport *ast) override;
+ bool visit(AST::ImportsList *ast) override;
+ bool visit(AST::NamedImports *ast) override;
+ bool visit(AST::ImportClause *ast) override;
+
+ bool visit(AST::ExportDeclaration *ast) override;
+ bool visit(AST::ExportClause *ast) override;
+ bool visit(AST::ExportSpecifier *ast) override;
+ bool visit(AST::ExportsList *ast) override;
+
+ bool visit(AST::FromClause *ast) override;
+
+ void endVisit(AST::ComputedPropertyName *) override;
+
+ void endVisit(AST::ExportDeclaration *ast) override;
+ void endVisit(AST::ExportClause *ast) override;
+
+ void endVisit(AST::ImportDeclaration *ast) override;
+ void endVisit(AST::NamedImports *ast) override;
+
+ void throwRecursionDepthError() override;
+
+private:
+ bool addSemicolons() const { return expressionDepth > 0; }
+
+ OutWriter &lw;
+ std::shared_ptr<AstComments> comments;
+ std::function<QStringView(SourceLocation)> loc2Str;
+ QHash<AST::Node *, QList<std::function<void()>>> postOps;
+ int expressionDepth = 0;
+};
+
+QMLDOM_EXPORT void reformatAst(
+ OutWriter &lw, const std::shared_ptr<AstComments> &comments,
+ const std::function<QStringView(SourceLocation)> &loc2Str, AST::Node *n);
+
+} // namespace Dom
+} // namespace QQmlJS
+QT_END_NAMESPACE
+
+#endif // QQMLDOMREFORMATTER_P
diff --git a/src/qmldom/qqmldomscanner.cpp b/src/qmldom/qqmldomscanner.cpp
new file mode 100644
index 0000000000..84cd591fb0
--- /dev/null
+++ b/src/qmldom/qqmldomscanner.cpp
@@ -0,0 +1,448 @@
+// Copyright (C) 2022 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+
+#include "qqmldomscanner_p.h"
+#include "qqmldomerrormessage_p.h"
+
+#include <QtCore/QMetaEnum>
+
+#include <algorithm>
+
+QT_BEGIN_NAMESPACE
+
+using namespace QQmlJS::Dom;
+
+static void addLexToken(QList<Token> &tokens, int tokenKind, QQmlJS::Lexer &lexer,
+ bool &regexpMayFollow)
+{
+ switch (tokenKind) {
+ case QQmlJSGrammar::T_DIVIDE_:
+ case QQmlJSGrammar::T_DIVIDE_EQ:
+ if (regexpMayFollow) {
+ QQmlJS::Lexer::RegExpBodyPrefix prefix;
+ if (tokenKind == QQmlJSGrammar::T_DIVIDE_)
+ prefix = QQmlJS::Lexer::NoPrefix;
+ else
+ prefix = QQmlJS::Lexer::EqualPrefix;
+ if (lexer.scanRegExp(prefix)) {
+ regexpMayFollow = false;
+ break;
+ } else {
+ qCWarning(domLog) << "lexing error scannign regexp in" << lexer.code()
+ << lexer.errorCode() << lexer.errorMessage();
+ }
+ break;
+ } else if (tokenKind == QQmlJSGrammar::T_DIVIDE_) {
+ regexpMayFollow = true;
+ }
+ Q_FALLTHROUGH();
+
+ case QQmlJSGrammar::T_AND:
+ case QQmlJSGrammar::T_AND_AND:
+ case QQmlJSGrammar::T_AND_EQ:
+ case QQmlJSGrammar::T_ARROW:
+ case QQmlJSGrammar::T_EQ:
+ case QQmlJSGrammar::T_EQ_EQ:
+ case QQmlJSGrammar::T_EQ_EQ_EQ:
+ case QQmlJSGrammar::T_GE:
+ case QQmlJSGrammar::T_GT:
+ case QQmlJSGrammar::T_GT_GT:
+ case QQmlJSGrammar::T_GT_GT_EQ:
+ case QQmlJSGrammar::T_GT_GT_GT:
+ case QQmlJSGrammar::T_GT_GT_GT_EQ:
+ case QQmlJSGrammar::T_LE:
+ case QQmlJSGrammar::T_LT:
+ case QQmlJSGrammar::T_LT_LT:
+ case QQmlJSGrammar::T_LT_LT_EQ:
+ case QQmlJSGrammar::T_MINUS:
+ case QQmlJSGrammar::T_MINUS_EQ:
+ case QQmlJSGrammar::T_MINUS_MINUS:
+ case QQmlJSGrammar::T_NOT:
+ case QQmlJSGrammar::T_NOT_EQ:
+ case QQmlJSGrammar::T_NOT_EQ_EQ:
+ case QQmlJSGrammar::T_OR:
+ case QQmlJSGrammar::T_OR_EQ:
+ case QQmlJSGrammar::T_OR_OR:
+ case QQmlJSGrammar::T_PLUS:
+ case QQmlJSGrammar::T_PLUS_EQ:
+ case QQmlJSGrammar::T_PLUS_PLUS:
+ case QQmlJSGrammar::T_QUESTION:
+ case QQmlJSGrammar::T_QUESTION_DOT:
+ case QQmlJSGrammar::T_QUESTION_QUESTION:
+ case QQmlJSGrammar::T_REMAINDER:
+ case QQmlJSGrammar::T_REMAINDER_EQ:
+ case QQmlJSGrammar::T_STAR:
+ case QQmlJSGrammar::T_STAR_EQ:
+ case QQmlJSGrammar::T_STAR_STAR:
+ case QQmlJSGrammar::T_STAR_STAR_EQ:
+ case QQmlJSGrammar::T_TILDE:
+ case QQmlJSGrammar::T_XOR:
+ case QQmlJSGrammar::T_XOR_EQ:
+
+ case QQmlJSGrammar::T_AT:
+
+ case QQmlJSGrammar::T_AUTOMATIC_SEMICOLON:
+ case QQmlJSGrammar::T_COMPATIBILITY_SEMICOLON:
+ case QQmlJSGrammar::T_SEMICOLON:
+
+ case QQmlJSGrammar::T_COLON:
+ case QQmlJSGrammar::T_COMMA:
+ case QQmlJSGrammar::T_LBRACE:
+ case QQmlJSGrammar::T_LBRACKET:
+ case QQmlJSGrammar::T_LPAREN:
+
+ case QQmlJSGrammar::T_ELLIPSIS:
+ regexpMayFollow = true;
+ break;
+
+ case QQmlJSGrammar::T_FUNCTION:
+ // might contain a space at the end...
+ tokens.append(Token(lexer.tokenStartColumn() - 1,
+ lexer.tokenLength() - ((lexer.tokenText().endsWith(u' ')) ? 1 : 0),
+ tokenKind));
+ return;
+
+ case QQmlJSGrammar::T_DOT:
+ case QQmlJSGrammar::T_RBRACE:
+ case QQmlJSGrammar::T_RBRACKET:
+ case QQmlJSGrammar::T_RPAREN:
+ regexpMayFollow = false;
+ break;
+
+ // template used to expand to a string plus a delimiter for the ${ and }, now
+ // we use a + as delimiter
+ case QQmlJSGrammar::T_TEMPLATE_HEAD:
+ regexpMayFollow = true;
+ tokens.append(Token(lexer.tokenStartColumn() - 1, lexer.tokenLength() - 2, tokenKind));
+ tokens.append(Token(lexer.tokenStartColumn() + lexer.tokenLength() - 3, 2,
+ QQmlJSGrammar::T_PLUS));
+ return;
+ case QQmlJSGrammar::T_TEMPLATE_MIDDLE:
+ regexpMayFollow = true;
+ tokens.append(Token(lexer.tokenStartColumn() - 1, 1, QQmlJSGrammar::T_PLUS));
+ tokens.append(Token(lexer.tokenStartColumn(), lexer.tokenLength() - 3, tokenKind));
+ tokens.append(Token(lexer.tokenStartColumn() + lexer.tokenLength() - 3, 2,
+ QQmlJSGrammar::T_PLUS));
+ return;
+ case QQmlJSGrammar::T_TEMPLATE_TAIL:
+ regexpMayFollow = true;
+ tokens.append(Token(lexer.tokenStartColumn() - 1, 1, QQmlJSGrammar::T_PLUS));
+ tokens.append(Token(lexer.tokenStartColumn(), lexer.tokenLength() - 1, tokenKind));
+ return;
+ case QQmlJSGrammar::T_PARTIAL_TEMPLATE_MIDDLE:
+ regexpMayFollow = true;
+ tokens.append(Token(lexer.tokenStartColumn() - 1, 1, QQmlJSGrammar::T_PLUS));
+ tokens.append(Token(lexer.tokenStartColumn(), lexer.tokenLength() - 1, tokenKind));
+ return;
+ case QQmlJSGrammar::T_MULTILINE_STRING_LITERAL:
+ case QQmlJSGrammar::T_NO_SUBSTITUTION_TEMPLATE:
+ case QQmlJSGrammar::T_STRING_LITERAL:
+ case QQmlJSGrammar::T_PARTIAL_SINGLE_QUOTE_STRING_LITERAL:
+ case QQmlJSGrammar::T_PARTIAL_DOUBLE_QUOTE_STRING_LITERAL:
+ case QQmlJSGrammar::T_PARTIAL_TEMPLATE_HEAD:
+ regexpMayFollow = (tokenKind == QQmlJSGrammar::T_TEMPLATE_MIDDLE
+ || tokenKind == QQmlJSGrammar::T_TEMPLATE_HEAD);
+ break;
+
+ case QQmlJSGrammar::T_VERSION_NUMBER:
+ if (lexer.state().currentChar == u'.') {
+ int offset = lexer.tokenStartColumn() - 1;
+ int length = lexer.tokenLength();
+ tokenKind = lexer.lex();
+ Q_ASSERT(tokenKind == QQmlJSGrammar::T_DOT);
+ tokenKind = lexer.lex();
+ Q_ASSERT(tokenKind == QQmlJSGrammar::T_VERSION_NUMBER);
+ length += 1 + lexer.tokenLength();
+ tokens.append(Token(offset, length, QQmlJSGrammar::T_NUMERIC_LITERAL));
+ return;
+ }
+ break;
+
+ default:
+ break;
+ }
+ // avoid newline (on multiline comments/strings)
+ qsizetype len = lexer.code().size();
+ if (lexer.code().endsWith(u'\n'))
+ --len;
+ len -= lexer.tokenStartColumn() - 1;
+ if (len < 0)
+ len = 0;
+ if (lexer.tokenLength() < len)
+ len = lexer.tokenLength();
+ tokens.append(Token(lexer.tokenStartColumn() - 1, len, tokenKind));
+}
+
+bool Token::lexKindIsDelimiter(int kind)
+{
+ switch (kind) {
+ case QQmlJSGrammar::T_AND:
+ case QQmlJSGrammar::T_AND_AND:
+ case QQmlJSGrammar::T_AND_EQ:
+ case QQmlJSGrammar::T_ARROW:
+ case QQmlJSGrammar::T_EQ:
+ case QQmlJSGrammar::T_EQ_EQ:
+ case QQmlJSGrammar::T_EQ_EQ_EQ:
+ case QQmlJSGrammar::T_GE:
+ case QQmlJSGrammar::T_GT:
+ case QQmlJSGrammar::T_GT_GT:
+ case QQmlJSGrammar::T_GT_GT_EQ:
+ case QQmlJSGrammar::T_GT_GT_GT:
+ case QQmlJSGrammar::T_GT_GT_GT_EQ:
+ case QQmlJSGrammar::T_LE:
+ case QQmlJSGrammar::T_LT:
+ case QQmlJSGrammar::T_LT_LT:
+ case QQmlJSGrammar::T_LT_LT_EQ:
+ case QQmlJSGrammar::T_MINUS:
+ case QQmlJSGrammar::T_MINUS_EQ:
+ case QQmlJSGrammar::T_MINUS_MINUS:
+ case QQmlJSGrammar::T_NOT:
+ case QQmlJSGrammar::T_NOT_EQ:
+ case QQmlJSGrammar::T_NOT_EQ_EQ:
+ case QQmlJSGrammar::T_OR:
+ case QQmlJSGrammar::T_OR_EQ:
+ case QQmlJSGrammar::T_OR_OR:
+ case QQmlJSGrammar::T_PLUS:
+ case QQmlJSGrammar::T_PLUS_EQ:
+ case QQmlJSGrammar::T_PLUS_PLUS:
+ case QQmlJSGrammar::T_QUESTION:
+ case QQmlJSGrammar::T_QUESTION_DOT:
+ case QQmlJSGrammar::T_QUESTION_QUESTION:
+ case QQmlJSGrammar::T_REMAINDER:
+ case QQmlJSGrammar::T_REMAINDER_EQ:
+ case QQmlJSGrammar::T_STAR:
+ case QQmlJSGrammar::T_STAR_EQ:
+ case QQmlJSGrammar::T_STAR_STAR:
+ case QQmlJSGrammar::T_STAR_STAR_EQ:
+ case QQmlJSGrammar::T_TILDE:
+ case QQmlJSGrammar::T_XOR:
+ case QQmlJSGrammar::T_XOR_EQ:
+
+ case QQmlJSGrammar::T_AT:
+ return true;
+ default:
+ break;
+ }
+ return false;
+}
+
+bool Token::lexKindIsQmlReserved(int kind)
+{
+ switch (kind) {
+ case QQmlJSGrammar::T_AS:
+ case QQmlJSGrammar::T_IMPORT:
+ case QQmlJSGrammar::T_SIGNAL:
+ case QQmlJSGrammar::T_PROPERTY:
+ case QQmlJSGrammar::T_READONLY:
+ case QQmlJSGrammar::T_COMPONENT:
+ case QQmlJSGrammar::T_REQUIRED:
+ case QQmlJSGrammar::T_ON:
+ case QQmlJSGrammar::T_ENUM:
+ return true;
+ default:
+ break;
+ }
+ return false;
+}
+
+bool Token::lexKindIsComment(int kind)
+{
+ switch (kind) {
+ case QQmlJSGrammar::T_COMMENT:
+ case QQmlJSGrammar::T_PARTIAL_COMMENT:
+ return true;
+ default:
+ break;
+ }
+ return false;
+}
+
+bool Token::lexKindIsJSKeyword(int kind)
+{
+ switch (kind) {
+ case QQmlJSGrammar::T_BREAK:
+ case QQmlJSGrammar::T_CASE:
+ case QQmlJSGrammar::T_CATCH:
+ case QQmlJSGrammar::T_CLASS:
+ case QQmlJSGrammar::T_CONST:
+ case QQmlJSGrammar::T_CONTINUE:
+ case QQmlJSGrammar::T_DEBUGGER:
+ case QQmlJSGrammar::T_DEFAULT:
+ case QQmlJSGrammar::T_DELETE:
+ case QQmlJSGrammar::T_DO:
+ case QQmlJSGrammar::T_ELSE:
+ case QQmlJSGrammar::T_ENUM:
+ case QQmlJSGrammar::T_EXPORT:
+ case QQmlJSGrammar::T_EXTENDS:
+ case QQmlJSGrammar::T_FALSE:
+ case QQmlJSGrammar::T_FINALLY:
+ case QQmlJSGrammar::T_FOR:
+ case QQmlJSGrammar::T_FROM:
+ case QQmlJSGrammar::T_GET:
+ case QQmlJSGrammar::T_IF:
+ case QQmlJSGrammar::T_IN:
+ case QQmlJSGrammar::T_INSTANCEOF:
+ case QQmlJSGrammar::T_LET:
+ case QQmlJSGrammar::T_NEW:
+ case QQmlJSGrammar::T_RETURN:
+ case QQmlJSGrammar::T_SUPER:
+ case QQmlJSGrammar::T_SWITCH:
+ case QQmlJSGrammar::T_THEN:
+ case QQmlJSGrammar::T_THIS:
+ case QQmlJSGrammar::T_THROW:
+ case QQmlJSGrammar::T_VOID:
+ case QQmlJSGrammar::T_WHILE:
+ case QQmlJSGrammar::T_WITH:
+ case QQmlJSGrammar::T_YIELD:
+ case QQmlJSGrammar::T_VAR:
+ case QQmlJSGrammar::T_FUNCTION_STAR:
+ case QQmlJSGrammar::T_FUNCTION:
+ return true;
+ default:
+ break;
+ }
+ return false;
+}
+
+bool Token::lexKindIsIdentifier(int kind)
+{
+ switch (kind) {
+ case QQmlJSGrammar::T_IDENTIFIER:
+ case QQmlJSGrammar::T_COMPONENT:
+ case QQmlJSGrammar::T_REQUIRED:
+ case QQmlJSGrammar::T_AS:
+ case QQmlJSGrammar::T_PRAGMA:
+ case QQmlJSGrammar::T_IMPORT:
+ case QQmlJSGrammar::T_RESERVED_WORD:
+ case QQmlJSGrammar::T_SET:
+ case QQmlJSGrammar::T_SIGNAL:
+ case QQmlJSGrammar::T_PROPERTY:
+ case QQmlJSGrammar::T_PUBLIC:
+ case QQmlJSGrammar::T_READONLY:
+ case QQmlJSGrammar::T_NULL:
+ case QQmlJSGrammar::T_OF:
+ case QQmlJSGrammar::T_ON:
+ case QQmlJSGrammar::T_STATIC:
+ case QQmlJSGrammar::T_TRUE:
+ case QQmlJSGrammar::T_TRY:
+ case QQmlJSGrammar::T_TYPEOF:
+ case QQmlJSGrammar::T_WITHOUTAS:
+ return true;
+ default:
+ break;
+ }
+ return false;
+}
+
+bool Token::lexKindIsStringType(int kind)
+{
+ switch (kind) {
+ case QQmlJSGrammar::T_PARTIAL_TEMPLATE_MIDDLE:
+ case QQmlJSGrammar::T_MULTILINE_STRING_LITERAL:
+ case QQmlJSGrammar::T_NO_SUBSTITUTION_TEMPLATE:
+ case QQmlJSGrammar::T_STRING_LITERAL:
+ case QQmlJSGrammar::T_PARTIAL_SINGLE_QUOTE_STRING_LITERAL:
+ case QQmlJSGrammar::T_PARTIAL_DOUBLE_QUOTE_STRING_LITERAL:
+ case QQmlJSGrammar::T_PARTIAL_TEMPLATE_HEAD:
+ return true;
+ default:
+ break;
+ }
+ return false;
+}
+
+bool Token::lexKindIsInvalid(int kind)
+{
+ switch (kind) {
+ case QQmlJSGrammar::T_NONE:
+ case QQmlJSGrammar::T_EOL:
+ case QQmlJSGrammar::EOF_SYMBOL:
+ case QQmlJSGrammar::T_ERROR:
+ case QQmlJSGrammar::T_FEED_JS_EXPRESSION:
+ case QQmlJSGrammar::T_FEED_JS_MODULE:
+ case QQmlJSGrammar::T_FEED_JS_SCRIPT:
+ case QQmlJSGrammar::T_FEED_JS_STATEMENT:
+ case QQmlJSGrammar::T_FEED_UI_OBJECT_MEMBER:
+ case QQmlJSGrammar::T_FEED_UI_PROGRAM:
+ case QQmlJSGrammar::REDUCE_HERE:
+ case QQmlJSGrammar::T_FORCE_BLOCK:
+ case QQmlJSGrammar::T_FORCE_DECLARATION:
+ case QQmlJSGrammar::T_FOR_LOOKAHEAD_OK:
+ return true;
+ default:
+ break;
+ }
+ return false;
+}
+
+void Token::dump(const Sink &s, QStringView line) const
+{
+ s(u"{");
+ sinkInt(s, offset);
+ s(u", ");
+ sinkInt(s, length);
+ s(u", Token::");
+ s(QString::number(lexKind));
+ s(u"}");
+ QStringView value = line.mid(offset, length);
+ if (!value.isEmpty()) {
+ s(u":");
+ sinkEscaped(s, value);
+ }
+}
+
+QList<Token> Scanner::operator()(QStringView text, const Scanner::State &startState)
+{
+ _state = startState;
+ QList<Token> tokens;
+
+ {
+ QQmlJS::Lexer lexer(nullptr, QQmlJS::Lexer::LexMode::LineByLine);
+ lexer.setState(startState.state);
+ QString line = text.toString();
+ if (!(line.endsWith(u"\n") || line.endsWith(u"\r")))
+ line += u'\n';
+ lexer.setCode(line, -1, _qmlMode, QQmlJS::Lexer::CodeContinuation::Continue);
+ while (true) {
+ int tokenKind = lexer.lex();
+ if (tokenKind == QQmlJSGrammar::T_EOL || tokenKind == QQmlJSGrammar::EOF_SYMBOL)
+ break;
+ addLexToken(tokens, tokenKind, lexer, _state.regexpMightFollow);
+ }
+ _state.state = lexer.state();
+ }
+ return tokens;
+}
+
+Scanner::State Scanner::state() const
+{
+ return _state;
+}
+
+bool Scanner::State::isMultiline() const
+{
+ switch (state.tokenKind) {
+ case QQmlJSGrammar::T_PARTIAL_COMMENT:
+ case QQmlJSGrammar::T_PARTIAL_DOUBLE_QUOTE_STRING_LITERAL:
+ case QQmlJSGrammar::T_PARTIAL_SINGLE_QUOTE_STRING_LITERAL:
+ case QQmlJSGrammar::T_PARTIAL_TEMPLATE_HEAD:
+ case QQmlJSGrammar::T_PARTIAL_TEMPLATE_MIDDLE:
+ return true;
+ default:
+ break;
+ }
+ return false;
+}
+
+bool Scanner::State::isMultilineComment() const
+{
+ switch (state.tokenKind) {
+ case QQmlJSGrammar::T_PARTIAL_COMMENT:
+ return true;
+ default:
+ break;
+ }
+ return false;
+}
+
+QT_END_NAMESPACE
diff --git a/src/qmldom/qqmldomscanner_p.h b/src/qmldom/qqmldomscanner_p.h
new file mode 100644
index 0000000000..5ff1c5b767
--- /dev/null
+++ b/src/qmldom/qqmldomscanner_p.h
@@ -0,0 +1,98 @@
+// Copyright (C) 2022 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+
+#ifndef QQMLDOMSCANNER_P_H
+#define QQMLDOMSCANNER_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 "qqmldomstringdumper_p.h"
+
+#include <QStringList>
+#include <QStringView>
+#include <QtQml/private/qqmljslexer_p.h>
+#include <QtQml/private/qqmljsgrammar_p.h>
+
+QT_BEGIN_NAMESPACE
+
+namespace QQmlJS {
+namespace Dom {
+
+class QMLDOM_EXPORT Token
+{
+ Q_GADGET
+public:
+ static bool lexKindIsDelimiter(int kind);
+ static bool lexKindIsJSKeyword(int kind);
+ static bool lexKindIsIdentifier(int kind);
+ static bool lexKindIsStringType(int kind);
+ static bool lexKindIsInvalid(int kind);
+ static bool lexKindIsQmlReserved(int kind);
+ static bool lexKindIsComment(int kind);
+
+ inline Token() = default;
+ inline Token(int o, int l, int lexKind) : offset(o), length(l), lexKind(lexKind) { }
+ inline int begin() const { return offset; }
+ inline int end() const { return offset + length; }
+ void dump(const Sink &s, QStringView line = QStringView()) const;
+ QString toString(QStringView line = QStringView()) const
+ {
+ return dumperToString([line, this](const Sink &s) { this->dump(s, line); });
+ }
+
+ static int compare(const Token &t1, const Token &t2)
+ {
+ if (int c = t1.offset - t2.offset)
+ return c;
+ if (int c = t1.length - t2.length)
+ return c;
+ return int(t1.lexKind) - int(t2.lexKind);
+ }
+
+ int offset = 0;
+ int length = 0;
+ int lexKind = QQmlJSGrammar::T_NONE;
+};
+
+inline int operator==(const Token &t1, const Token &t2)
+{
+ return Token::compare(t1, t2) == 0;
+}
+inline int operator!=(const Token &t1, const Token &t2)
+{
+ return Token::compare(t1, t2) != 0;
+}
+
+class QMLDOM_EXPORT Scanner
+{
+public:
+ struct QMLDOM_EXPORT State
+ {
+ Lexer::State state {};
+ bool regexpMightFollow = true;
+ bool isMultiline() const;
+ bool isMultilineComment() const;
+ };
+
+ QList<Token> operator()(QStringView text, const State &startState);
+ State state() const;
+
+private:
+ bool _qmlMode = true;
+ State _state;
+};
+
+} // namespace Dom
+} // namespace QQmlJS
+QT_END_NAMESPACE
+#endif
diff --git a/src/qmldom/qqmldomscriptelements.cpp b/src/qmldom/qqmldomscriptelements.cpp
new file mode 100644
index 0000000000..4bde2abd09
--- /dev/null
+++ b/src/qmldom/qqmldomscriptelements.cpp
@@ -0,0 +1,355 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+
+#include "qqmldom_utils_p.h"
+#include "qqmldomitem_p.h"
+#include "qqmldompath_p.h"
+#include "qqmldomscriptelements_p.h"
+#include <memory>
+#include <utility>
+#include <variant>
+
+using namespace QQmlJS::Dom::ScriptElements;
+using QQmlJS::Dom::DomType;
+using QQmlJS::Dom::ScriptElement;
+using QQmlJS::Dom::ScriptElementVariant;
+
+/*!
+ \internal
+ \class ScriptElementBase
+
+ The base class for all script elements.
+
+ Derived classes should implement createFileLocations, DomElement::updatePathFromOwner and
+ DomBase::iterateDirectSubpaths. Furthermore, they need their own DomType enum.
+
+ updatePathFromOwner and createFileLocations should be called on the script element root node
+ after it was constructed for the DomItem-wrapping to work correctly. Without it, methods like
+ iterateDirectSubpaths and all the stuff in DomItem will not work.
+
+ createFileLocations does not work without having the pathFromOwner set
+ first via updatePathFromOwner.
+
+ In derived classes, the updatePathFromOwner-implementation should call the base implementation
+ and also call recursively updatePathFromOwner on the derived class's children.
+
+ See \l ScriptElementBase::createFileLocations for the createFileLocations implementation in
+ derived classes.
+
+ Derived classes need to implement iterateDirectSubpaths to comply with the DomItem interface.
+*/
+
+/*!
+ \internal
+ \fn ScriptElementBase::createFileLocations
+
+ Usually, all the visits/recursive calls to DOM elements can be done using the DomItem interface,
+ once all the DOM has been constructed.
+
+ During construction, createFileLocations can be used to annotate the DOM representation with the
+ corresponding source locations, which are needed, e.g., to find the corresponding DOM element
+ from a certain text position. When called, createFileLocations sets an entry for itself in the
+ FileLocationsTree.
+
+ Derived classes should call the base implemenatation and recursively call createFileLocations on
+ all their children.
+
+ Usually, only the root of the script DOM element requires one createFileLocations call after
+ construction \b{and} after a pathFromOwner was set using updatePathFromOwner.
+
+*/
+
+/*!
+ \internal
+ \class ScriptList
+
+ A Helper class for writing script elements that contain lists, helps for implementing the
+ recursive calls of iterateDirectSubpaths, updatePathFromOwner and createFileLocations.
+*/
+
+/*!
+ \internal
+ Helper for fields with elements in iterateDirectSubpaths.
+ */
+static bool wrap(const QQmlJS::Dom::DomItem &self, QQmlJS::Dom::DirectVisitor visitor, QStringView field,
+ const ScriptElementVariant &value)
+{
+ if (!value)
+ return true;
+
+ const bool b =
+ self.dvItemField(visitor, field, [&self, field, &value]() -> QQmlJS::Dom::DomItem {
+ const QQmlJS::Dom::Path pathFromOwner{ self.pathFromOwner().field(field) };
+ return self.subScriptElementWrapperItem(value);
+ });
+ return b;
+}
+
+/*!
+ \internal
+ Helper for fields with lists in iterateDirectSubpaths.
+ */
+static bool wrap(const QQmlJS::Dom::DomItem &self, QQmlJS::Dom::DirectVisitor visitor, QStringView field,
+ const ScriptList &value)
+{
+ const bool b =
+ self.dvItemField(visitor, field, [&self, field, &value]() -> QQmlJS::Dom::DomItem {
+ const QQmlJS::Dom::Path pathFromOwner{ self.pathFromOwner().field(field) };
+ return self.subListItem(value.asList(pathFromOwner));
+ });
+ return b;
+}
+
+bool GenericScriptElement::iterateDirectSubpaths(const DomItem &self, DirectVisitor visitor) const
+{
+ bool cont = true;
+ for (auto it = m_children.begin(); it != m_children.end(); ++it) {
+ cont &= std::visit(
+ [&self, &visitor, &it](auto &&e) { return wrap(self, visitor, it->first, e); },
+ it->second);
+ }
+ return cont;
+}
+
+void GenericScriptElement::updatePathFromOwner(const Path &p)
+{
+ BaseT::updatePathFromOwner(p);
+ for (auto it = m_children.begin(); it != m_children.end(); ++it) {
+ std::visit(qOverloadedVisitor{ [&p, &it](ScriptElementVariant &e) {
+ e.base()->updatePathFromOwner(p.field(it->first));
+ },
+ [&p, &it](ScriptList &list) {
+ list.updatePathFromOwner(p.field(it->first));
+ } },
+ it->second);
+ }
+}
+
+void GenericScriptElement::createFileLocations(const FileLocations::Tree &base)
+{
+ BaseT::createFileLocations(base);
+ for (auto it = m_children.begin(); it != m_children.end(); ++it) {
+ std::visit(
+ qOverloadedVisitor{
+ [&base](ScriptElementVariant &e) { e.base()->createFileLocations(base); },
+ [&base](ScriptList &list) { list.createFileLocations(base); } },
+ it->second);
+ }
+}
+
+bool BlockStatement::iterateDirectSubpaths(const DomItem &self, DirectVisitor visitor) const
+{
+ // TODO: test me
+ bool cont = true;
+ cont &= wrap(self, visitor, Fields::statements, m_statements);
+ return cont;
+}
+
+void BlockStatement::updatePathFromOwner(const Path &p)
+{
+ BaseT::updatePathFromOwner(p);
+ m_statements.updatePathFromOwner(p.field(Fields::statements));
+}
+
+void BlockStatement::createFileLocations(const FileLocations::Tree &base)
+{
+ BaseT::createFileLocations(base);
+ m_statements.createFileLocations(base);
+}
+
+bool IdentifierExpression::iterateDirectSubpaths(const DomItem &self, DirectVisitor visitor) const
+{
+ bool cont = true;
+ cont &= self.dvValueField(visitor, Fields::identifier, m_name);
+ return cont;
+}
+
+bool Literal::iterateDirectSubpaths(const DomItem &self, DirectVisitor visitor) const
+{
+ bool cont = true;
+ std::visit([&cont, &visitor,
+ &self](auto &&e) { cont &= self.dvValueField(visitor, Fields::value, e); },
+ m_value);
+ return cont;
+}
+
+bool IfStatement::iterateDirectSubpaths(const DomItem &self, DirectVisitor visitor) const
+{
+ // TODO: test me
+ bool cont = true;
+ cont &= wrap(self, visitor, Fields::condition, m_condition);
+ cont &= wrap(self, visitor, Fields::consequence, m_consequence);
+ cont &= wrap(self, visitor, Fields::alternative, m_alternative);
+ return cont;
+}
+
+void IfStatement::updatePathFromOwner(const Path &p)
+{
+ BaseT::updatePathFromOwner(p);
+ if (auto ptr = m_condition.base())
+ ptr->updatePathFromOwner(p.field(Fields::condition));
+ if (auto ptr = m_consequence.base())
+ ptr->updatePathFromOwner(p.field(Fields::consequence));
+ if (auto ptr = m_alternative.base())
+ ptr->updatePathFromOwner(p.field(Fields::alternative));
+}
+
+void IfStatement::createFileLocations(const FileLocations::Tree &base)
+{
+ BaseT::createFileLocations(base);
+ if (auto ptr = m_condition.base())
+ ptr->createFileLocations(base);
+ if (auto ptr = m_consequence.base())
+ ptr->createFileLocations(base);
+ if (auto ptr = m_alternative.base())
+ ptr->createFileLocations(base);
+}
+
+bool ForStatement::iterateDirectSubpaths(const DomItem &self, DirectVisitor visitor) const
+{
+ bool cont = true;
+ cont &= wrap(self, visitor, Fields::initializer, m_initializer);
+ cont &= wrap(self, visitor, Fields::declarations, m_declarations);
+ cont &= wrap(self, visitor, Fields::condition, m_condition);
+ cont &= wrap(self, visitor, Fields::expression, m_expression);
+ cont &= wrap(self, visitor, Fields::body, m_body);
+ return cont;
+}
+
+void ForStatement::updatePathFromOwner(const Path &p)
+{
+ BaseT::updatePathFromOwner(p);
+ if (auto ptr = m_initializer.base())
+ ptr->updatePathFromOwner(p.field(Fields::initializer));
+ if (auto ptr = m_declarations.base())
+ ptr->updatePathFromOwner(p.field(Fields::declarations));
+ if (auto ptr = m_condition.base())
+ ptr->updatePathFromOwner(p.field(Fields::condition));
+ if (auto ptr = m_expression.base())
+ ptr->updatePathFromOwner(p.field(Fields::expression));
+ if (auto ptr = m_body.base())
+ ptr->updatePathFromOwner(p.field(Fields::body));
+}
+
+void ForStatement::createFileLocations(const FileLocations::Tree &base)
+{
+ BaseT::createFileLocations(base);
+ if (auto ptr = m_initializer.base())
+ ptr->createFileLocations(base);
+ if (auto ptr = m_declarations.base())
+ ptr->createFileLocations(base);
+ if (auto ptr = m_condition.base())
+ ptr->createFileLocations(base);
+ if (auto ptr = m_expression.base())
+ ptr->createFileLocations(base);
+ if (auto ptr = m_body.base())
+ ptr->createFileLocations(base);
+}
+
+bool BinaryExpression::iterateDirectSubpaths(const DomItem &self, DirectVisitor visitor) const
+{
+ bool cont = true;
+ cont &= wrap(self, visitor, Fields::left, m_left);
+ cont &= self.dvValueField(visitor, Fields::operation, m_operator);
+ cont &= wrap(self, visitor, Fields::right, m_right);
+ return cont;
+}
+
+void BinaryExpression::updatePathFromOwner(const Path &p)
+{
+ BaseT::updatePathFromOwner(p);
+ if (auto ptr = m_left.base())
+ ptr->updatePathFromOwner(p.field(Fields::left));
+ if (auto ptr = m_right.base())
+ ptr->updatePathFromOwner(p.field(Fields::right));
+}
+
+void BinaryExpression::createFileLocations(const FileLocations::Tree &base)
+{
+ BaseT::createFileLocations(base);
+ if (auto ptr = m_left.base())
+ ptr->createFileLocations(base);
+ if (auto ptr = m_right.base())
+ ptr->createFileLocations(base);
+}
+
+bool VariableDeclarationEntry::iterateDirectSubpaths(const DomItem &self, DirectVisitor visitor) const
+{
+ bool cont = true;
+ cont &= self.dvValueField(visitor, Fields::scopeType, m_scopeType);
+ cont &= wrap(self, visitor, Fields::identifier, m_identifier);
+ cont &= wrap(self, visitor, Fields::initializer, m_initializer);
+ return cont;
+}
+
+void VariableDeclarationEntry::updatePathFromOwner(const Path &p)
+{
+ BaseT::updatePathFromOwner(p);
+ if (auto ptr = m_identifier.base())
+ ptr->updatePathFromOwner(p.field(Fields::identifier));
+ if (auto ptr = m_initializer.base())
+ ptr->updatePathFromOwner(p.field(Fields::initializer));
+}
+
+void VariableDeclarationEntry::createFileLocations(const FileLocations::Tree &base)
+{
+ BaseT::createFileLocations(base);
+ if (auto ptr = m_identifier.base())
+ ptr->createFileLocations(base);
+ if (auto ptr = m_initializer.base())
+ ptr->createFileLocations(base);
+}
+
+bool VariableDeclaration::iterateDirectSubpaths(const DomItem &self, DirectVisitor visitor) const
+{
+ bool cont = true;
+ cont &= wrap(self, visitor, Fields::declarations, m_declarations);
+ return cont;
+}
+
+void VariableDeclaration::updatePathFromOwner(const Path &p)
+{
+ BaseT::updatePathFromOwner(p);
+ m_declarations.updatePathFromOwner(p.field(Fields::declarations));
+}
+
+void VariableDeclaration::createFileLocations(const FileLocations::Tree &base)
+{
+ BaseT::createFileLocations(base);
+ m_declarations.createFileLocations(base);
+}
+
+bool ReturnStatement::iterateDirectSubpaths(const DomItem &self, DirectVisitor visitor) const
+{
+ bool cont = true;
+ cont &= wrap(self, visitor, Fields::expression, m_expression);
+ return cont;
+}
+
+void ReturnStatement::updatePathFromOwner(const Path &p)
+{
+ BaseT::updatePathFromOwner(p);
+ if (auto ptr = m_expression.base())
+ ptr->updatePathFromOwner(p.field(Fields::expression));
+}
+
+void ReturnStatement::createFileLocations(const FileLocations::Tree &base)
+{
+ BaseT::createFileLocations(base);
+ if (auto ptr = m_expression.base())
+ ptr->createFileLocations(base);
+}
+
+void ScriptList::replaceKindForGenericChildren(DomType oldType, DomType newType)
+{
+ for (auto &it : m_list) {
+ if (auto current = it.data()) {
+ if (auto genericElement =
+ std::get_if<std::shared_ptr<ScriptElements::GenericScriptElement>>(
+ &*current)) {
+ if ((*genericElement)->kind() == oldType)
+ (*genericElement)->setKind(newType);
+ }
+ }
+ }
+}
diff --git a/src/qmldom/qqmldomscriptelements_p.h b/src/qmldom/qqmldomscriptelements_p.h
new file mode 100644
index 0000000000..b319e7e88f
--- /dev/null
+++ b/src/qmldom/qqmldomscriptelements_p.h
@@ -0,0 +1,405 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+
+#ifndef QQMLDOMSCRIPTELEMENTS_P_H
+#define QQMLDOMSCRIPTELEMENTS_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 "qqmldomitem_p.h"
+#include "qqmldomattachedinfo_p.h"
+#include "qqmldompath_p.h"
+#include <algorithm>
+#include <limits>
+#include <type_traits>
+#include <utility>
+#include <variant>
+
+QT_BEGIN_NAMESPACE
+
+namespace QQmlJS {
+namespace Dom {
+
+namespace ScriptElements {
+
+template<DomType type>
+class ScriptElementBase : public ScriptElement
+{
+public:
+ using BaseT = ScriptElementBase<type>;
+ static constexpr DomType kindValue = type;
+ static constexpr DomKind domKindValue = DomKind::ScriptElement;
+
+ ScriptElementBase(QQmlJS::SourceLocation combinedLocation = QQmlJS::SourceLocation{})
+ : ScriptElement(), m_locations({ { FileLocationRegion::MainRegion, combinedLocation } })
+ {
+ }
+ ScriptElementBase(QQmlJS::SourceLocation first, QQmlJS::SourceLocation last)
+ : ScriptElementBase(combine(first, last))
+ {
+ }
+ DomType kind() const override { return type; }
+ DomKind domKind() const override { return domKindValue; }
+
+ void createFileLocations(const FileLocations::Tree &base) override
+ {
+ FileLocations::Tree res =
+ FileLocations::ensure(base, pathFromOwner(), AttachedInfo::PathType::Relative);
+ for (auto location: m_locations) {
+ FileLocations::addRegion(res, location.first, location.second);
+ }
+ }
+
+ /*
+ Pretty prints the current DomItem. Currently, for script elements, this is done entirely on
+ the parser representation (via the AST classes), but it could be moved here if needed.
+ */
+ // void writeOut(const DomItem &self, OutWriter &lw) const override;
+
+ /*!
+ All of the following overloads are only required for optimization purposes.
+ The base implementation will work fine, but might be slightly slower.
+ You can override dump(), fields(), field(), indexes(), index(), keys() or key() if the
+ performance of the base class becomes problematic.
+ */
+
+ // // needed for debug
+ // void dump(const DomItem &, const Sink &sink, int indent, FilterT filter) const override;
+
+ // // just required for optimization if iterateDirectSubpaths is slow
+ // QList<QString> fields(const DomItem &self) const override;
+ // DomItem field(const DomItem &self, QStringView name) const override;
+
+ // index_type indexes(const DomItem &self) const override;
+ // DomItem index(const DomItem &self, index_type index) const override;
+
+ // QSet<QString> const keys(const DomItem &self) const override;
+ // DomItem key(const DomItem &self, const QString &name) const override;
+
+ QQmlJS::SourceLocation mainRegionLocation() const
+ {
+ Q_ASSERT(m_locations.size() > 0);
+ Q_ASSERT(m_locations.front().first == FileLocationRegion::MainRegion);
+
+ auto current = m_locations.front();
+ return current.second;
+ }
+ void setMainRegionLocation(const QQmlJS::SourceLocation &location)
+ {
+ Q_ASSERT(m_locations.size() > 0);
+ Q_ASSERT(m_locations.front().first == FileLocationRegion::MainRegion);
+
+ m_locations.front().second = location;
+ }
+ void addLocation(FileLocationRegion region, QQmlJS::SourceLocation location)
+ {
+ Q_ASSERT_X(region != FileLocationRegion::MainRegion, "ScriptElementBase::addLocation",
+ "use the setCombinedLocation instead!");
+ m_locations.emplace_back(region, location);
+ }
+
+protected:
+ std::vector<std::pair<FileLocationRegion, QQmlJS::SourceLocation>> m_locations;
+};
+
+class ScriptList : public ScriptElementBase<DomType::List>
+{
+public:
+ using typename ScriptElementBase<DomType::List>::BaseT;
+
+ using BaseT::BaseT;
+
+ // minimal required overload for this to be wrapped as DomItem:
+ bool iterateDirectSubpaths(const DomItem &self, DirectVisitor visitor) const override
+ {
+ bool cont =
+ asList(self.pathFromOwner().key(QString())).iterateDirectSubpaths(self, visitor);
+ return cont;
+ }
+ void updatePathFromOwner(const Path &p) override
+ {
+ BaseT::updatePathFromOwner(p);
+ for (int i = 0; i < m_list.size(); ++i) {
+ Q_ASSERT(m_list[i].base());
+ m_list[i].base()->updatePathFromOwner(p.index(i));
+ }
+ }
+ void createFileLocations(const FileLocations::Tree &base) override
+ {
+ BaseT::createFileLocations(base);
+
+ for (int i = 0; i < m_list.size(); ++i) {
+ Q_ASSERT(m_list[i].base());
+ m_list[i].base()->createFileLocations(base);
+ }
+ }
+
+ List asList(const Path &path) const
+ {
+ auto asList = List::fromQList<ScriptElementVariant>(
+ path, m_list,
+ [](const DomItem &list, const PathEls::PathComponent &, const ScriptElementVariant &wrapped)
+ -> DomItem { return list.subScriptElementWrapperItem(wrapped); });
+
+ return asList;
+ }
+
+ void append(const ScriptElementVariant &statement) { m_list.push_back(statement); }
+ void append(const ScriptList &list) { m_list.append(list.m_list); }
+ void reverse() { std::reverse(m_list.begin(), m_list.end()); }
+ void replaceKindForGenericChildren(DomType oldType, DomType newType);
+ const QList<ScriptElementVariant> &qList() { return std::as_const(m_list); };
+
+private:
+ QList<ScriptElementVariant> m_list;
+};
+
+class GenericScriptElement : public ScriptElementBase<DomType::ScriptGenericElement>
+{
+public:
+ using BaseT::BaseT;
+ using VariantT = std::variant<ScriptElementVariant, ScriptList>;
+
+ bool iterateDirectSubpaths(const DomItem &self, DirectVisitor visitor) const override;
+ void updatePathFromOwner(const Path &p) override;
+ void createFileLocations(const FileLocations::Tree &base) override;
+
+ DomType kind() const override { return m_kind; }
+ void setKind(DomType kind) { m_kind = kind; }
+
+ decltype(auto) insertChild(QStringView name, VariantT v)
+ {
+ return m_children.insert(std::make_pair(name, v));
+ }
+
+private:
+ /*!
+ \internal
+ The DomItem interface will use iterateDirectSubpaths for all kinds of operations on the
+ GenericScriptElement. Therefore, to avoid bad surprises when using the DomItem interface, use
+ a sorted map to always iterate the children in the same order.
+ */
+ std::map<QQmlJS::Dom::FieldType, VariantT> m_children;
+ DomType m_kind = DomType::Empty;
+};
+
+class BlockStatement : public ScriptElementBase<DomType::ScriptBlockStatement>
+{
+public:
+ using BaseT::BaseT;
+
+ // minimal required overload for this to be wrapped as DomItem:
+ bool iterateDirectSubpaths(const DomItem &self, DirectVisitor visitor) const override;
+ void updatePathFromOwner(const Path &p) override;
+ void createFileLocations(const FileLocations::Tree &base) override;
+
+ ScriptList statements() const { return m_statements; }
+ void setStatements(const ScriptList &statements) { m_statements = statements; }
+
+private:
+ ScriptList m_statements;
+};
+
+class IdentifierExpression : public ScriptElementBase<DomType::ScriptIdentifierExpression>
+{
+public:
+ using BaseT::BaseT;
+ void setName(QStringView name) { m_name = name.toString(); }
+ QString name() { return m_name; }
+
+ // minimal required overload for this to be wrapped as DomItem:
+ bool iterateDirectSubpaths(const DomItem &self, DirectVisitor visitor) const override;
+
+ QCborValue value() const override { return QCborValue(m_name); }
+
+private:
+ QString m_name;
+};
+
+class Literal : public ScriptElementBase<DomType::ScriptLiteral>
+{
+public:
+ using BaseT::BaseT;
+
+ using VariantT = std::variant<QString, double, bool, std::nullptr_t>;
+
+ void setLiteralValue(VariantT value) { m_value = value; }
+ VariantT literalValue() const { return m_value; }
+
+ // minimal required overload for this to be wrapped as DomItem:
+ bool iterateDirectSubpaths(const DomItem &self, DirectVisitor visitor) const override;
+
+ QCborValue value() const override
+ {
+ return std::visit([](auto &&e) -> QCborValue { return e; }, m_value);
+ }
+
+private:
+ VariantT m_value;
+};
+
+// TODO: test this method + implement foreach etc
+class ForStatement : public ScriptElementBase<DomType::ScriptForStatement>
+{
+public:
+ using BaseT::BaseT;
+
+ // minimal required overload for this to be wrapped as DomItem:
+ bool iterateDirectSubpaths(const DomItem &self, DirectVisitor visitor) const override;
+ void updatePathFromOwner(const Path &p) override;
+ void createFileLocations(const FileLocations::Tree &base) override;
+
+ ScriptElementVariant initializer() const { return m_initializer; }
+ void setInitializer(const ScriptElementVariant &newInitializer)
+ {
+ m_initializer = newInitializer;
+ }
+
+ ScriptElementVariant declarations() const { return m_declarations; }
+ void setDeclarations(const ScriptElementVariant &newDeclaration)
+ {
+ m_declarations = newDeclaration;
+ }
+ ScriptElementVariant condition() const { return m_condition; }
+ void setCondition(const ScriptElementVariant &newCondition) { m_condition = newCondition; }
+ ScriptElementVariant expression() const { return m_expression; }
+ void setExpression(const ScriptElementVariant &newExpression) { m_expression = newExpression; }
+ ScriptElementVariant body() const { return m_body; }
+ void setBody(const ScriptElementVariant &newBody) { m_body = newBody; }
+
+private:
+ ScriptElementVariant m_initializer;
+ ScriptElementVariant m_declarations;
+ ScriptElementVariant m_condition;
+ ScriptElementVariant m_expression;
+ ScriptElementVariant m_body;
+};
+
+class IfStatement : public ScriptElementBase<DomType::ScriptIfStatement>
+{
+public:
+ using BaseT::BaseT;
+
+ // minimal required overload for this to be wrapped as DomItem:
+ bool iterateDirectSubpaths(const DomItem &self, DirectVisitor visitor) const override;
+ void updatePathFromOwner(const Path &p) override;
+ void createFileLocations(const FileLocations::Tree &base) override;
+
+ ScriptElementVariant condition() const { return m_condition; }
+ void setCondition(const ScriptElementVariant &condition) { m_condition = condition; }
+ ScriptElementVariant consequence() { return m_consequence; }
+ void setConsequence(const ScriptElementVariant &consequence) { m_consequence = consequence; }
+ ScriptElementVariant alternative() { return m_alternative; }
+ void setAlternative(const ScriptElementVariant &alternative) { m_alternative = alternative; }
+
+private:
+ ScriptElementVariant m_condition;
+ ScriptElementVariant m_consequence;
+ ScriptElementVariant m_alternative;
+};
+
+class ReturnStatement : public ScriptElementBase<DomType::ScriptReturnStatement>
+{
+public:
+ using BaseT::BaseT;
+
+ // minimal required overload for this to be wrapped as DomItem:
+ bool iterateDirectSubpaths(const DomItem &self, DirectVisitor visitor) const override;
+ void updatePathFromOwner(const Path &p) override;
+ void createFileLocations(const FileLocations::Tree &base) override;
+
+ ScriptElementVariant expression() const { return m_expression; }
+ void setExpression(ScriptElementVariant expression) { m_expression = expression; }
+
+private:
+ ScriptElementVariant m_expression;
+};
+
+class BinaryExpression : public ScriptElementBase<DomType::ScriptBinaryExpression>
+{
+public:
+ using BaseT::BaseT;
+
+ enum Operator : char {
+ FieldMemberAccess,
+ ArrayMemberAccess,
+ TO_BE_IMPLEMENTED = std::numeric_limits<char>::max(), // not required by qmlls
+ };
+
+ // minimal required overload for this to be wrapped as DomItem:
+ bool iterateDirectSubpaths(const DomItem &self, DirectVisitor visitor) const override;
+ void updatePathFromOwner(const Path &p) override;
+ void createFileLocations(const FileLocations::Tree &base) override;
+
+ ScriptElementVariant left() const { return m_left; }
+ void setLeft(const ScriptElementVariant &newLeft) { m_left = newLeft; }
+ ScriptElementVariant right() const { return m_right; }
+ void setRight(const ScriptElementVariant &newRight) { m_right = newRight; }
+ int op() const { return m_operator; }
+ void setOp(Operator op) { m_operator = op; }
+
+private:
+ ScriptElementVariant m_left;
+ ScriptElementVariant m_right;
+ Operator m_operator = TO_BE_IMPLEMENTED;
+};
+
+class VariableDeclarationEntry : public ScriptElementBase<DomType::ScriptVariableDeclarationEntry>
+{
+public:
+ using BaseT::BaseT;
+
+ enum ScopeType { Var, Let, Const };
+
+ ScopeType scopeType() const { return m_scopeType; }
+ void setScopeType(ScopeType scopeType) { m_scopeType = scopeType; }
+
+ ScriptElementVariant identifier() const { return m_identifier; }
+ void setIdentifier(const ScriptElementVariant &identifier) { m_identifier = identifier; }
+
+ ScriptElementVariant initializer() const { return m_initializer; }
+ void setInitializer(const ScriptElementVariant &initializer) { m_initializer = initializer; }
+
+ bool iterateDirectSubpaths(const DomItem &self, DirectVisitor visitor) const override;
+ void updatePathFromOwner(const Path &p) override;
+ void createFileLocations(const FileLocations::Tree &base) override;
+
+private:
+ ScopeType m_scopeType;
+ ScriptElementVariant m_identifier;
+ ScriptElementVariant m_initializer;
+};
+
+class VariableDeclaration : public ScriptElementBase<DomType::ScriptVariableDeclaration>
+{
+public:
+ using BaseT::BaseT;
+
+ // minimal required overload for this to be wrapped as DomItem:
+ bool iterateDirectSubpaths(const DomItem &self, DirectVisitor visitor) const override;
+ void updatePathFromOwner(const Path &p) override;
+ void createFileLocations(const FileLocations::Tree &base) override;
+
+ void setDeclarations(const ScriptList &list) { m_declarations = list; }
+ ScriptList declarations() { return m_declarations; }
+
+private:
+ ScriptList m_declarations;
+};
+
+} // namespace ScriptElements
+} // end namespace Dom
+} // end namespace QQmlJS
+
+QT_END_NAMESPACE
+
+#endif // QQMLDOMSCRIPTELEMENTS_P_H
diff --git a/src/qmldom/qqmldomstringdumper.cpp b/src/qmldom/qqmldomstringdumper.cpp
index 1788d79d3d..4859614f77 100644
--- a/src/qmldom/qqmldomstringdumper.cpp
+++ b/src/qmldom/qqmldomstringdumper.cpp
@@ -1,40 +1,6 @@
-/****************************************************************************
-**
-** 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$
-**/
+// Copyright (C) 2020 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+
#include "qqmldomstringdumper_p.h"
#include <QtCore/QDebug>
@@ -68,7 +34,7 @@ namespace Dom {
* \brief Helper class to accept eithe a string or a dumper (a function that writes to a sink)
*
* Using a Dumper as input parameter one always obtains a dumper (i.e. a
- * std::function<void(std::function<void(QStringView)>)> , but can pass in any
+ * function_ref<void(function_ref<void(QStringView)>)> , but can pass in any
* object accepted by QStringView, and it is automatically converted to a dumper.
*/
@@ -77,7 +43,7 @@ namespace Dom {
* \brief Converts a dumper to a string
* \param writer The dumper convert to a string
*/
-QString dumperToString(Dumper writer)
+QString dumperToString(const Dumper &writer)
{
QString s;
QTextStream d(&s);
@@ -93,11 +59,11 @@ QString dumperToString(Dumper writer)
* \param s The string to sink
* \param options If quotes should be outputted around the string (defaults to yes)
*/
-void sinkEscaped(Sink sink, QStringView s, EscapeOptions options) {
+void sinkEscaped(const Sink &sink, QStringView s, EscapeOptions options) {
if (options == EscapeOptions::OuterQuotes)
sink(u"\"");
int it0=0;
- for (int it = 0; it < s.length();++it) {
+ for (int it = 0; it < s.size();++it) {
QChar c=s[it];
bool noslash = c != QLatin1Char('\\');
bool noquote = c != QLatin1Char('"');
@@ -118,7 +84,7 @@ void sinkEscaped(Sink sink, QStringView s, EscapeOptions options) {
else
Q_ASSERT(0);
}
- sink(s.mid(it0, s.length() - it0));
+ sink(s.mid(it0, s.size() - it0));
if (options == EscapeOptions::OuterQuotes)
sink(u"\"");
}
@@ -129,7 +95,7 @@ void sinkEscaped(Sink sink, QStringView s, EscapeOptions options) {
* \param s the sink to write to
* \param level the level to describe
*/
-void dumpErrorLevel(Sink s, ErrorLevel level)
+void dumpErrorLevel(const Sink &s, ErrorLevel level)
{
switch (level) {
case ErrorLevel::Debug:
@@ -138,18 +104,9 @@ void dumpErrorLevel(Sink s, ErrorLevel level)
case ErrorLevel::Info:
s(u"Info");
break;
- case ErrorLevel::Hint:
- s(u"Hint");
- break;
- case ErrorLevel::MaybeWarning:
- s(u"MaybeWarn");
- break;
case ErrorLevel::Warning:
s(u"Warning");
break;
- case ErrorLevel::MaybeError:
- s(u"MaybeErr");
- break;
case ErrorLevel::Error:
s(u"Error");
break;
@@ -160,7 +117,7 @@ void dumpErrorLevel(Sink s, ErrorLevel level)
}
-void dumperToQDebug(Dumper dumper, QDebug debug)
+void dumperToQDebug(const Dumper &dumper, QDebug debug)
{
QDebug & d = debug.noquote().nospace();
dumper([&d](QStringView s){
@@ -174,21 +131,18 @@ void dumperToQDebug(Dumper dumper, QDebug debug)
* \param level the error level of the message
* \param dumper the dumper that writes a message
*/
-void dumperToQDebug(Dumper dumper, ErrorLevel level)
+void dumperToQDebug(const Dumper &dumper, ErrorLevel level)
{
QDebug d = qDebug().noquote().nospace();
switch (level) {
case ErrorLevel::Debug:
break;
case ErrorLevel::Info:
- case ErrorLevel::Hint:
d = qInfo().noquote().nospace();
break;
- case ErrorLevel::MaybeWarning:
case ErrorLevel::Warning:
d = qWarning().noquote().nospace();
break;
- case ErrorLevel::MaybeError:
case ErrorLevel::Error:
case ErrorLevel::Fatal: // should be handled differently (avoid allocations...), we try to catch them before ending up here
d = qCritical().noquote().nospace();
@@ -203,13 +157,13 @@ void dumperToQDebug(Dumper dumper, ErrorLevel level)
* \internal
* \brief sinks the requested amount of spaces
*/
-void sinkIndent(Sink s, int indent)
+void sinkIndent(const Sink &s, int indent)
{
if (indent > 0) {
QStringView spaces = u" ";
- while (indent > spaces.length()) {
+ while (indent > spaces.size()) {
s(spaces);
- indent -= spaces.length();
+ indent -= spaces.size();
}
s(spaces.left(indent));
}
@@ -219,7 +173,7 @@ void sinkIndent(Sink s, int indent)
* \internal
* \brief sinks a neline and indents by the given amount
*/
-void sinkNewline(Sink s, int indent)
+void sinkNewline(const Sink &s, int indent)
{
s(u"\n");
if (indent > 0)
@@ -232,6 +186,13 @@ void sinkNewline(Sink s, int indent)
* \brief A sink that ignores whatever it receives
*/
+QDebug operator<<(QDebug d, const Dumper &dumper)
+{
+ QDebug dd = d.noquote().nospace();
+ dumper([&dd](QStringView s) { dd << s; });
+ return d;
+}
+
} // end namespace Dom
} // end namespace QQmlJS
diff --git a/src/qmldom/qqmldomstringdumper_p.h b/src/qmldom/qqmldomstringdumper_p.h
index 7669bfc882..cf5c8e6483 100644
--- a/src/qmldom/qqmldomstringdumper_p.h
+++ b/src/qmldom/qqmldomstringdumper_p.h
@@ -1,40 +1,6 @@
-/****************************************************************************
-**
-** 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$
-**/
+// Copyright (C) 2020 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+
#ifndef DUMPER_H
#define DUMPER_H
@@ -51,6 +17,7 @@
#include "qqmldom_global.h"
#include "qqmldomconstants_p.h"
+#include "qqmldomfunctionref_p.h"
#include <QtCore/QString>
#include <QtCore/QStringView>
@@ -63,8 +30,9 @@ QT_BEGIN_NAMESPACE
namespace QQmlJS {
namespace Dom {
-using Sink = std::function<void(QStringView)>;
-using DumperFunction = std::function<void(Sink)>;
+using Sink = function_ref<void(QStringView)>;
+using SinkF = std::function<void(QStringView)>;
+using DumperFunction = std::function<void(const Sink &)>;
class Dumper{
public:
@@ -73,9 +41,9 @@ private:
// We want to avoid the limit of one user conversion:
// after doing (* -> QStringView) we cannot have QStringView -> Dumper, as it
// would be the second user defined conversion.
- // For a similar reason we have a template to accept function<void(Sink)> .
+ // For a similar reason we have a template to accept function_ref<void(Sink)> .
// The end result is that void f(Dumper) can be called nicely, and avoid overloads:
- // f(u"bla"), f(QLatin1String("bla")), f(QString()), f([](Sink s){...}),...
+ // f(u"bla"), f(QLatin1String("bla")), f(QString()), f([](const Sink &s){...}),...
template <typename T>
using if_compatible_dumper = typename
std::enable_if<std::is_convertible<T, DumperFunction>::value, bool>::type;
@@ -86,7 +54,7 @@ private:
public:
Dumper(QStringView s):
- dumper([s](Sink sink){ sink(s); }) {}
+ dumper([s](const Sink &sink){ sink(s); }) {}
Dumper(std::nullptr_t): Dumper(QStringView(nullptr)) {}
@@ -95,13 +63,13 @@ public:
Dumper(QStringView(string)) {}
template <typename U, if_compatible_dumper<U> = true>
- Dumper(U f): dumper(f) {}
+ Dumper(U f): dumper(std::move(f)) {}
- void operator()(Sink s) { dumper(s); }
+ void operator()(const Sink &s) const { dumper(s); }
};
template <typename T>
-void sinkInt(Sink s, T i) {
+void sinkInt(const Sink &s, T i) {
const int BUFSIZE = 42; // safe up to 128 bits
QChar buf[BUFSIZE];
int ibuf = BUFSIZE;
@@ -128,22 +96,24 @@ void sinkInt(Sink s, T i) {
s(QStringView(&buf[ibuf], BUFSIZE - ibuf -1));
}
-QMLDOM_EXPORT QString dumperToString(Dumper writer);
+QMLDOM_EXPORT QString dumperToString(const Dumper &writer);
-QMLDOM_EXPORT void sinkEscaped(Sink sink, QStringView s,
+QMLDOM_EXPORT void sinkEscaped(const Sink &sink, QStringView s,
EscapeOptions options = EscapeOptions::OuterQuotes);
inline void devNull(QStringView) {}
-QMLDOM_EXPORT void sinkIndent(Sink s, int indent);
+QMLDOM_EXPORT void sinkIndent(const Sink &s, int indent);
+
+QMLDOM_EXPORT void sinkNewline(const Sink &s, int indent = 0);
-QMLDOM_EXPORT void sinkNewline(Sink s, int indent = 0);
+QMLDOM_EXPORT void dumpErrorLevel(const Sink &s, ErrorLevel level);
-QMLDOM_EXPORT void dumpErrorLevel(Sink s, ErrorLevel level);
+QMLDOM_EXPORT void dumperToQDebug(const Dumper &dumper, QDebug debug);
-QMLDOM_EXPORT void dumperToQDebug(Dumper dumper, QDebug debug);
+QMLDOM_EXPORT void dumperToQDebug(const Dumper &dumper, ErrorLevel level = ErrorLevel::Debug);
-QMLDOM_EXPORT void dumperToQDebug(Dumper dumper, ErrorLevel level = ErrorLevel::Debug);
+QMLDOM_EXPORT QDebug operator<<(QDebug d, const Dumper &dumper);
} // end namespace Dom
} // end namespace QQmlJS
diff --git a/src/qmldom/qqmldomtop.cpp b/src/qmldom/qqmldomtop.cpp
index 57259c1659..8ae836b0a2 100644
--- a/src/qmldom/qqmldomtop.cpp
+++ b/src/qmldom/qqmldomtop.cpp
@@ -1,41 +1,15 @@
-/****************************************************************************
-**
-** 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$
-**/
+// Copyright (C) 2020 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+
+#include "qqmldomitem_p.h"
#include "qqmldomtop_p.h"
+#include "qqmldomexternalitems_p.h"
+#include "qqmldommock_p.h"
+#include "qqmldomelements_p.h"
+#include "qqmldomastcreator_p.h"
+#include "qqmldommoduleindex_p.h"
+#include "qqmldomtypesreader_p.h"
+#include "qqmldom_utils_p.h"
#include <QtQml/private/qqmljslexer_p.h>
#include <QtQml/private/qqmljsparser_p.h>
@@ -43,19 +17,25 @@
#include <QtQml/private/qqmljsastvisitor_p.h>
#include <QtQml/private/qqmljsast_p.h>
+#include <QtCore/QBasicMutex>
+#include <QtCore/QCborArray>
+#include <QtCore/QDebug>
+#include <QtCore/QDir>
#include <QtCore/QFile>
#include <QtCore/QFileInfo>
-#include <QtCore/QScopeGuard>
-#include <QtCore/QRegularExpression>
#include <QtCore/QPair>
-#include <QtCore/QCborArray>
-#include <QtCore/QDebug>
-#include <QtCore/QBasicMutex>
+#include <QtCore/QRegularExpression>
+#include <QtCore/QScopeGuard>
+#if QT_FEATURE_thread
+# include <QtCore/QThread>
+#endif
#include <memory>
QT_BEGIN_NAMESPACE
+using namespace Qt::StringLiterals;
+
namespace QQmlJS {
namespace Dom {
@@ -75,11 +55,6 @@ using std::shared_ptr;
if force is true the file is always read
*/
-Path DomTop::pathFromOwner(const DomItem &) const
-{
- return Path();
-}
-
Path DomTop::canonicalPath(const DomItem &) const
{
return canonicalPath();
@@ -90,14 +65,24 @@ DomItem DomTop::containingObject(const DomItem &) const
return DomItem();
}
-bool DomTop::iterateDirectSubpaths(DomItem &self, function<bool (Path, DomItem &)> visitor)
+bool DomTop::iterateDirectSubpaths(const DomItem &self, DirectVisitor visitor) const
{
+ static QHash<QString, QString> knownFields;
+ static QBasicMutex m;
+ auto toField = [](const QString &f) mutable -> QStringView {
+ QMutexLocker l(&m);
+ if (!knownFields.contains(f))
+ knownFields[f] = f;
+ return knownFields[f];
+ };
bool cont = true;
auto objs = m_extraOwningItems;
auto itO = objs.cbegin();
auto endO = objs.cend();
while (itO != endO) {
- cont = cont && self.copy(*itO).toSubField(itO.key()).visit(visitor);
+ cont = cont && self.dvItemField(visitor, toField(itO.key()), [&self, &itO]() {
+ return std::visit([&self](auto &&el) { return self.copy(el); }, *itO);
+ });
++itO;
}
return cont;
@@ -109,22 +94,13 @@ void DomTop::clearExtraOwningItems()
m_extraOwningItems.clear();
}
-QMap<QString, std::shared_ptr<OwningItem> > DomTop::extraOwningItems() const
+QMap<QString, OwnerT> DomTop::extraOwningItems() const
{
QMutexLocker l(mutex());
- QMap<QString, std::shared_ptr<OwningItem> > res = m_extraOwningItems;
+ QMap<QString, OwnerT> res = m_extraOwningItems;
return res;
}
-void DomTop::setExtraOwningItem(QString fieldName, std::shared_ptr<OwningItem> item)
-{
- QMutexLocker l(mutex());
- if (!item)
- m_extraOwningItems.remove(fieldName);
- else
- m_extraOwningItems.insert(fieldName, item);
-}
-
/*!
\class QQmlJS::Dom::DomUniverse
@@ -145,38 +121,78 @@ ErrorGroups DomUniverse::myErrors()
return groups;
}
-DomUniverse::DomUniverse(QString universeName, Options options):
- m_name(universeName), m_options(options)
-{}
+DomUniverse::DomUniverse(const QString &universeName) : m_name(universeName) { }
+
+std::shared_ptr<DomUniverse> DomUniverse::guaranteeUniverse(
+ const std::shared_ptr<DomUniverse> &univ)
+{
+ const auto next = [] {
+ Q_CONSTINIT static std::atomic<int> counter(0);
+ return counter.fetch_add(1, std::memory_order_relaxed) + 1;
+ };
+ if (univ)
+ return univ;
+
+ return std::make_shared<DomUniverse>(
+ QLatin1String("universe") + QString::number(next()));
+}
+
+DomItem DomUniverse::create(const QString &universeName)
+{
+ auto res = std::make_shared<DomUniverse>(universeName);
+ return DomItem(res);
+}
Path DomUniverse::canonicalPath() const
{
- return Path::root(u"universe");
+ return Path::Root(u"universe");
}
-bool DomUniverse::iterateDirectSubpaths(DomItem &self, function<bool (Path, DomItem &)> visitor)
+bool DomUniverse::iterateDirectSubpaths(const DomItem &self, DirectVisitor visitor) const
{
bool cont = true;
cont = cont && DomTop::iterateDirectSubpaths(self, visitor);
- cont = cont && self.subDataField(Fields::name, name()).visit(visitor);
- cont = cont && self.subDataField(Fields::options, int(options())).visit(visitor);
- QQueue<ParsingTask> q = queue();
- cont = cont && self.subList(
- List(Path::field(Fields::queue),
- [q](const DomItem &list, index_type i){
- if (i >= 0 && i < q.length())
- return list.subDataIndex(i, q.at(i).toCbor(), ConstantData::Options::FirstMapIsFields).item;
- else
- return DomItem();
- }, [q](const DomItem &){
- return index_type(q.length());
- }, nullptr, QLatin1String("ParsingTask"))
- ).visit(visitor);
-
+ cont = cont && self.dvValueField(visitor, Fields::name, name());
+ cont = cont && self.dvItemField(visitor, Fields::globalScopeWithName, [this, &self]() {
+ return self.subMapItem(Map(
+ Path::Field(Fields::globalScopeWithName),
+ [this](const DomItem &map, const QString &key) { return map.copy(globalScopeWithName(key)); },
+ [this](const DomItem &) { return globalScopeNames(); }, QLatin1String("GlobalScope")));
+ });
+ cont = cont && self.dvItemField(visitor, Fields::qmlDirectoryWithPath, [this, &self]() {
+ return self.subMapItem(Map(
+ Path::Field(Fields::qmlDirectoryWithPath),
+ [this](const DomItem &map, const QString &key) { return map.copy(qmlDirectoryWithPath(key)); },
+ [this](const DomItem &) { return qmlDirectoryPaths(); }, QLatin1String("QmlDirectory")));
+ });
+ cont = cont && self.dvItemField(visitor, Fields::qmldirFileWithPath, [this, &self]() {
+ return self.subMapItem(Map(
+ Path::Field(Fields::qmldirFileWithPath),
+ [this](const DomItem &map, const QString &key) { return map.copy(qmldirFileWithPath(key)); },
+ [this](const DomItem &) { return qmldirFilePaths(); }, QLatin1String("QmldirFile")));
+ });
+ cont = cont && self.dvItemField(visitor, Fields::qmlFileWithPath, [this, &self]() {
+ return self.subMapItem(Map(
+ Path::Field(Fields::qmlFileWithPath),
+ [this](const DomItem &map, const QString &key) { return map.copy(qmlFileWithPath(key)); },
+ [this](const DomItem &) { return qmlFilePaths(); }, QLatin1String("QmlFile")));
+ });
+ cont = cont && self.dvItemField(visitor, Fields::jsFileWithPath, [this, &self]() {
+ return self.subMapItem(Map(
+ Path::Field(Fields::jsFileWithPath),
+ [this](const DomItem &map, const QString &key) { return map.copy(jsFileWithPath(key)); },
+ [this](const DomItem &) { return jsFilePaths(); }, QLatin1String("JsFile")));
+ });
+ cont = cont && self.dvItemField(visitor, Fields::jsFileWithPath, [this, &self]() {
+ return self.subMapItem(Map(
+ Path::Field(Fields::qmltypesFileWithPath),
+ [this](const DomItem &map, const QString &key) { return map.copy(qmltypesFileWithPath(key)); },
+ [this](const DomItem &) { return qmltypesFilePaths(); }, QLatin1String("QmltypesFile")));
+ });
return cont;
}
-std::shared_ptr<OwningItem> DomUniverse::doCopy(const DomItem &)
+std::shared_ptr<OwningItem> DomUniverse::doCopy(const DomItem &) const
{
QRegularExpression r(QRegularExpression::anchoredPattern(QLatin1String(R"(.*Copy([0-9]*)$)")));
auto m = r.match(m_name);
@@ -189,172 +205,1196 @@ std::shared_ptr<OwningItem> DomUniverse::doCopy(const DomItem &)
return res;
}
-void DomUniverse::loadFile(const DomItem &self, QString filePath, QString logicalPath, Callback callback, LoadOptions loadOptions)
+static DomType fileTypeForPath(const DomItem &self, const QString &canonicalFilePath)
{
- loadFile(self, filePath, logicalPath, QString(), QDateTime::fromMSecsSinceEpoch(0), callback, loadOptions);
+ if (canonicalFilePath.endsWith(u".qml", Qt::CaseInsensitive)
+ || canonicalFilePath.endsWith(u".qmlannotation", Qt::CaseInsensitive)) {
+ return DomType::QmlFile;
+ } else if (canonicalFilePath.endsWith(u".qmltypes")) {
+ return DomType::QmltypesFile;
+ } else if (QStringView(u"qmldir").compare(QFileInfo(canonicalFilePath).fileName(),
+ Qt::CaseInsensitive)
+ == 0) {
+ return DomType::QmldirFile;
+ } else if (QFileInfo(canonicalFilePath).isDir()) {
+ return DomType::QmlDirectory;
+ } else if (canonicalFilePath.endsWith(u".js", Qt::CaseInsensitive)
+ || canonicalFilePath.endsWith(u".mjs", Qt::CaseInsensitive)) {
+ return DomType::JsFile;
+ }
+ else {
+ self.addError(DomUniverse::myErrors()
+ .error(QCoreApplication::translate("Dom::fileTypeForPath",
+ "Could not detect type of file %1")
+ .arg(canonicalFilePath))
+ .handle());
+ }
+ return DomType::Empty;
}
-void DomUniverse::loadFile(const DomItem &self, QString canonicalFilePath, QString logicalPath, QString code, QDateTime codeDate, Callback callback, LoadOptions loadOptions)
+DomUniverse::LoadResult DomUniverse::loadFile(const FileToLoad &file, DomType fileType,
+ DomCreationOptions creationOptions)
{
- if (canonicalFilePath.endsWith(u".qml", Qt::CaseInsensitive) ||
- canonicalFilePath.endsWith(u".qmlannotation", Qt::CaseInsensitive) ||
- canonicalFilePath.endsWith(u".ui", Qt::CaseInsensitive)) {
- m_queue.enqueue(ParsingTask{
- QDateTime::currentDateTime(),
- loadOptions,
- DomType::QmlFile,
- canonicalFilePath,
- logicalPath,
- code,
- codeDate,
- self.ownerAs<DomUniverse>(),
- callback});
- } else if (canonicalFilePath.endsWith(u".qmltypes")) {
- m_queue.enqueue(ParsingTask{
- QDateTime::currentDateTime(),
- loadOptions,
- DomType::QmltypesFile,
- canonicalFilePath,
- logicalPath,
- code,
- codeDate,
- self.ownerAs<DomUniverse>(),
- callback});
- } else if (QStringView(u"qmldir").compare(QFileInfo(canonicalFilePath).fileName(), Qt::CaseInsensitive) == 0) {
- m_queue.enqueue(ParsingTask{
- QDateTime::currentDateTime(),
- loadOptions,
- DomType::QmldirFile,
- canonicalFilePath,
- logicalPath,
- code,
- codeDate,
- self.ownerAs<DomUniverse>(),
- callback});
- } else {
- self.addError(myErrors().error(tr("Ignoring request to load file of unknown type %1, calling callback immediately").arg(canonicalFilePath)).handle());
+ DomItem univ(shared_from_this());
+ switch (fileType) {
+ case DomType::QmlFile:
+ case DomType::QmltypesFile:
+ case DomType::QmldirFile:
+ case DomType::QmlDirectory:
+ case DomType::JsFile: {
+ LoadResult loadRes;
+ const auto &preLoadResult = preload(univ, file, fileType);
+ if (std::holds_alternative<LoadResult>(preLoadResult)) {
+ // universe already has the most recent version of the file
+ return std::get<LoadResult>(preLoadResult);
+ } else {
+ // content of the file needs to be parsed and value inside Universe needs to be updated
+ return load(std::get<ContentWithDate>(preLoadResult), file, fileType, creationOptions);
+ }
+ }
+ default:
+ univ.addError(myErrors()
+ .error(tr("Ignoring request to load file %1 of unexpected type %2, "
+ "calling callback immediately")
+ .arg(file.canonicalPath(), domTypeToString(fileType)))
+ .handle());
Q_ASSERT(false && "loading non supported file type");
- callback(Path(), DomItem(), DomItem());
+ return {};
+ }
+}
+
+DomUniverse::LoadResult DomUniverse::load(const ContentWithDate &codeWithDate,
+ const FileToLoad &file, DomType fType,
+ DomCreationOptions creationOptions)
+{
+ QString canonicalPath = file.canonicalPath();
+
+ DomItem oldValue; // old ExternalItemPair (might be empty, or equal to newValue)
+ DomItem newValue; // current ExternalItemPair
+ DomItem univ = DomItem(shared_from_this());
+
+ if (fType == DomType::QmlFile) {
+ auto qmlFile = parseQmlFile(codeWithDate.content, file, codeWithDate.date, creationOptions);
+ return insertOrUpdateExternalItem(std::move(qmlFile));
+ } else if (fType == DomType::QmltypesFile) {
+ auto qmltypesFile = std::make_shared<QmltypesFile>(canonicalPath, codeWithDate.content,
+ codeWithDate.date);
+ QmltypesReader reader(univ.copy(qmltypesFile));
+ reader.parse();
+ return insertOrUpdateExternalItem(std::move(qmltypesFile));
+ } else if (fType == DomType::QmldirFile) {
+ shared_ptr<QmldirFile> qmldirFile =
+ QmldirFile::fromPathAndCode(canonicalPath, codeWithDate.content);
+ return insertOrUpdateExternalItem(std::move(qmldirFile));
+ } else if (fType == DomType::QmlDirectory) {
+ auto qmlDirectory = std::make_shared<QmlDirectory>(
+ canonicalPath, codeWithDate.content.split(QLatin1Char('\n')), codeWithDate.date);
+ return insertOrUpdateExternalItem(std::move(qmlDirectory));
+ } else if (fType == DomType::JsFile) {
+ auto jsFile = parseJsFile(codeWithDate.content, file, codeWithDate.date);
+ return insertOrUpdateExternalItem(std::move(jsFile));
+ } else {
+ Q_ASSERT(false);
+ }
+ return { std::move(oldValue), std::move(newValue) };
+}
+
+/*!
+ \internal
+ This function is somewhat coupled and does the following:
+ 1. If a content of the file is provided it checks whether the item with the same content
+ already exists inside the Universe. If so, returns it as a result of the load
+ 2. If a content is not provided, it first tries to check whether Universe has the most
+ recent item. If yes, it returns it as a result of the load. Otherwise does step 1.
+ */
+DomUniverse::PreloadResult DomUniverse::preload(const DomItem &univ, const FileToLoad &file,
+ DomType fType) const
+{
+ QString canonicalPath = file.canonicalPath();
+ ContentWithDate codeWithDate;
+
+ if (file.content().has_value()) {
+ codeWithDate = { file.content()->data, file.content()->date };
+ } else {
+ // When content is empty, Universe attempts to read it from the File.
+ // However if it already has the most recent version of that File it just returns it
+ const auto &curValueItem = getItemIfMostRecent(univ, fType, canonicalPath);
+ if (curValueItem.has_value()) {
+ return LoadResult{ curValueItem.value(), curValueItem.value() };
+ }
+ // otherwise tries to read the content from the path
+ auto readResult = readFileContent(canonicalPath);
+ if (std::holds_alternative<ErrorMessage>(readResult)) {
+ DomItem newValue;
+ newValue.addError(std::move(std::get<ErrorMessage>(readResult)));
+ return LoadResult{ DomItem(), std::move(newValue) }; // read failed, nothing to parse
+ } else {
+ codeWithDate = std::get<ContentWithDate>(readResult);
+ }
+ }
+
+ // Once the code is provided Universe verifies if it already has an up-to-date code
+ const auto &curValueItem = getItemIfHasSameCode(univ, fType, canonicalPath, codeWithDate);
+ if (curValueItem.has_value()) {
+ return LoadResult{ curValueItem.value(), curValueItem.value() };
+ }
+ // otherwise code needs to be parsed
+ return codeWithDate;
+}
+
+void DomUniverse::removePath(const QString &path)
+{
+ QMutexLocker l(mutex());
+ const auto toDelete = [path](const auto &it) {
+ QString p = it.key();
+ return p.startsWith(path) && (p.size() == path.size() || p.at(path.size()) == u'/');
+ };
+ m_qmlDirectoryWithPath.removeIf(toDelete);
+ m_qmldirFileWithPath.removeIf(toDelete);
+ m_qmlFileWithPath.removeIf(toDelete);
+ m_jsFileWithPath.removeIf(toDelete);
+ m_qmltypesFileWithPath.removeIf(toDelete);
+}
+
+DomUniverse::ReadResult DomUniverse::readFileContent(const QString &canonicalPath) const
+{
+ if (canonicalPath.isEmpty()) {
+ return myErrors().error(tr("Non existing path %1").arg(canonicalPath));
+ }
+ QFile file(canonicalPath);
+ QFileInfo fileInfo(canonicalPath);
+ if (fileInfo.isDir()) {
+ return ContentWithDate{ QDir(canonicalPath)
+ .entryList(QDir::NoDotAndDotDot | QDir::Files, QDir::Name)
+ .join(QLatin1Char('\n')),
+ QDateTime::currentDateTimeUtc() };
+ }
+ if (!file.open(QIODevice::ReadOnly)) {
+ return myErrors().error(
+ tr("Error opening path %1: %2 %3")
+ .arg(canonicalPath, QString::number(file.error()), file.errorString()));
+ }
+ auto content = QString::fromUtf8(file.readAll());
+ file.close();
+ return ContentWithDate{ std::move(content), QDateTime::currentDateTimeUtc() };
+}
+
+std::shared_ptr<QmlFile> DomUniverse::parseQmlFile(const QString &code, const FileToLoad &file,
+ const QDateTime &contentDate,
+ DomCreationOptions creationOptions)
+{
+ auto qmlFile = std::make_shared<QmlFile>(file.canonicalPath(), code, contentDate, 0,
+ creationOptions.testFlag(WithRecovery)
+ ? QmlFile::EnableParserRecovery
+ : QmlFile::DisableParserRecovery);
+ std::shared_ptr<DomEnvironment> envPtr;
+ if (auto ptr = file.environment().lock())
+ envPtr = std::move(ptr);
+ else
+ envPtr = std::make_shared<DomEnvironment>(QStringList(),
+ DomEnvironment::Option::NoDependencies,
+ creationOptions, shared_from_this());
+ envPtr->addQmlFile(qmlFile);
+ DomItem env(envPtr);
+ if (qmlFile->isValid()) {
+ // do not call populateQmlFile twice on lazy qml files if the importer already does it!
+ if (!creationOptions.testFlag(DomCreationOption::WithSemanticAnalysis))
+ envPtr->populateFromQmlFile(MutableDomItem(env.copy(qmlFile)));
+ } else {
+ QString errs;
+ DomItem qmlFileObj = env.copy(qmlFile);
+ qmlFile->iterateErrors(qmlFileObj, [&errs](const DomItem &, const ErrorMessage &m) {
+ errs += m.toString();
+ errs += u"\n";
+ return true;
+ });
+ qCWarning(domLog).noquote().nospace()
+ << "Parsed invalid file " << file.canonicalPath() << errs;
+ }
+ return qmlFile;
+}
+
+std::shared_ptr<JsFile> DomUniverse::parseJsFile(const QString &code, const FileToLoad &file,
+ const QDateTime &contentDate)
+{
+ // WATCH OUT!
+ // DOM construction for plain JS files is not yet supported
+ // Only parsing of the file
+ // and adding ExternalItem to the Environment will happen here
+ auto jsFile = std::make_shared<JsFile>(file.canonicalPath(), code, contentDate);
+ std::shared_ptr<DomEnvironment> envPtr;
+ if (auto ptr = file.environment().lock())
+ envPtr = std::move(ptr);
+ else
+ envPtr = std::make_shared<DomEnvironment>(QStringList(),
+ DomEnvironment::Option::NoDependencies,
+ DomCreationOption::None, shared_from_this());
+ envPtr->addJsFile(jsFile);
+ DomItem env(envPtr);
+ if (!jsFile->isValid()) {
+ QString errs;
+ DomItem qmlFileObj = env.copy(jsFile);
+ jsFile->iterateErrors(qmlFileObj, [&errs](const DomItem &, const ErrorMessage &m) {
+ errs += m.toString();
+ errs += u"\n";
+ return true;
+ });
+ qCWarning(domLog).noquote().nospace()
+ << "Parsed invalid file " << file.canonicalPath() << errs;
+ }
+ return jsFile;
+}
+
+/*!
+ \internal
+ Queries the corresponding path map attempting to get the value
+ *WARNING* Usage of this function should be protected by the read lock
+ */
+std::shared_ptr<ExternalItemPairBase> DomUniverse::getPathValueOrNull(DomType fType,
+ const QString &path) const
+{
+ switch (fType) {
+ case DomType::QmlFile:
+ return m_qmlFileWithPath.value(path);
+ case DomType::QmltypesFile:
+ return m_qmltypesFileWithPath.value(path);
+ case DomType::QmldirFile:
+ return m_qmldirFileWithPath.value(path);
+ case DomType::QmlDirectory:
+ return m_qmlDirectoryWithPath.value(path);
+ case DomType::JsFile:
+ return m_jsFileWithPath.value(path);
+ default:
+ Q_ASSERT(false);
+ }
+ return nullptr;
+}
+
+std::optional<DomItem> DomUniverse::getItemIfMostRecent(const DomItem &univ, DomType fType,
+ const QString &canonicalPath) const
+{
+ QFileInfo fInfo(canonicalPath);
+ bool valueItemIsMostRecent = false;
+ std::shared_ptr<ExternalItemPairBase> value = nullptr;
+ {
+ // Mutex is to sync access to the Value and Value->CurrentItem, which can be modified
+ // through updateEnty method and currentItem->refreshedDataAt
+ QMutexLocker l(mutex());
+ value = getPathValueOrNull(fType, canonicalPath);
+ valueItemIsMostRecent = valueHasMostRecentItem(value.get(), fInfo.lastModified());
+ }
+ if (valueItemIsMostRecent) {
+ return univ.copy(value);
+ }
+ return std::nullopt;
+}
+
+std::optional<DomItem> DomUniverse::getItemIfHasSameCode(const DomItem &univ, DomType fType,
+ const QString &canonicalPath,
+ const ContentWithDate &codeWithDate) const
+{
+ std::shared_ptr<ExternalItemPairBase> value = nullptr;
+ bool valueItemHasSameCode = false;
+ {
+ // Mutex is to sync access to the Value and Value->CurrentItem, which can be modified
+ // through updateEnty method and currentItem->refreshedDataAt
+ QMutexLocker l(mutex());
+ value = getPathValueOrNull(fType, canonicalPath);
+ if (valueHasSameContent(value.get(), codeWithDate.content)) {
+ valueItemHasSameCode = true;
+ if (value->currentItem()->lastDataUpdateAt() < codeWithDate.date)
+ value->currentItem()->refreshedDataAt(codeWithDate.date);
+ }
+ }
+ if (valueItemHasSameCode) {
+ return univ.copy(value);
+ }
+ return std::nullopt;
+}
+
+/*!
+ \internal
+ Checks if value has current Item and if it was not modified since last seen
+ *WARNING* Usage of this function should be protected by the read lock
+ */
+bool DomUniverse::valueHasMostRecentItem(const ExternalItemPairBase *value,
+ const QDateTime &lastModified)
+{
+ if (!value || !value->currentItem()) {
+ return false;
+ }
+ return lastModified < value->currentItem()->lastDataUpdateAt();
+}
+
+/*!
+ \internal
+ Checks if value has current Item and if it has same content
+ *WARNING* Usage of this function should be protected by the read lock
+ */
+bool DomUniverse::valueHasSameContent(const ExternalItemPairBase *value, const QString &content)
+{
+ if (!value || !value->currentItem()) {
+ return false;
+ }
+ QString curContent = value->currentItem()->code();
+ return !curContent.isNull() && curContent == content;
+}
+
+std::shared_ptr<OwningItem> LoadInfo::doCopy(const DomItem &self) const
+{
+ auto res = std::make_shared<LoadInfo>(*this);
+ if (res->status() != Status::Done) {
+ res->addErrorLocal(DomEnvironment::myErrors().warning(
+ u"This is a copy of a LoadInfo still in progress, artificially ending it, if you "
+ u"use this you will *not* resume loading"));
+ DomEnvironment::myErrors()
+ .warning([&self](const Sink &sink) {
+ sink(u"Copying an in progress LoadInfo, which is most likely an error (");
+ self.dump(sink);
+ sink(u")");
+ })
+ .handle();
+ QMutexLocker l(res->mutex());
+ res->m_status = Status::Done;
+ res->m_toDo.clear();
+ res->m_inProgress.clear();
+ res->m_endCallbacks.clear();
+ }
+ return res;
+}
+
+Path LoadInfo::canonicalPath(const DomItem &) const
+{
+ return Path::Root(PathRoot::Env).field(Fields::loadInfo).key(elementCanonicalPath().toString());
+}
+
+bool LoadInfo::iterateDirectSubpaths(const DomItem &self, DirectVisitor visitor) const
+{
+ bool cont = OwningItem::iterateDirectSubpaths(self, visitor);
+ cont = cont && self.dvValueField(visitor, Fields::status, int(status()));
+ cont = cont && self.dvValueField(visitor, Fields::nLoaded, nLoaded());
+ cont = cont
+ && self.dvValueField(visitor, Fields::elementCanonicalPath,
+ elementCanonicalPath().toString());
+ cont = cont && self.dvValueField(visitor, Fields::nNotdone, nNotDone());
+ cont = cont && self.dvValueField(visitor, Fields::nCallbacks, nCallbacks());
+ return cont;
+}
+
+void LoadInfo::addEndCallback(const DomItem &self,
+ std::function<void(Path, const DomItem &, const DomItem &)> callback)
+{
+ if (!callback)
return;
+ {
+ QMutexLocker l(mutex());
+ switch (m_status) {
+ case Status::NotStarted:
+ case Status::Starting:
+ case Status::InProgress:
+ case Status::CallingCallbacks:
+ m_endCallbacks.append(callback);
+ return;
+ case Status::Done:
+ break;
+ }
}
- if (m_options & Option::SingleThreaded)
- execQueue(); // immediate execution in the same thread
+ Path p = elementCanonicalPath();
+ DomItem el = self.path(p);
+ callback(p, el, el);
}
-template <typename T>
-QPair<std::shared_ptr<ExternalItemPair<T>>,std::shared_ptr<ExternalItemPair<T>>> updateEntry(const DomItem &univ, std::shared_ptr<T> newItem, QMap<QString, std::shared_ptr<ExternalItemPair<T>>> &map, QBasicMutex *mutex)
+void LoadInfo::advanceLoad(const DomItem &self)
{
- std::shared_ptr<ExternalItemPair<T>> oldValue;
- std::shared_ptr<ExternalItemPair<T>> newValue;
- QString canonicalPath = newItem->canonicalFilePath();
- QDateTime now = QDateTime::currentDateTime();
+ Status myStatus;
+ Dependency dep;
+ bool depValid = false;
{
- QMutexLocker l(mutex);
- auto it = map.find(canonicalPath);
- if (it != map.cend() && (*it) && (*it)->current) {
- oldValue = *it;
- QString oldCode = oldValue->current->code();
- QString newCode = newItem->code();
- if (!oldCode.isNull() && !newCode.isNull() && oldCode == newCode) {
- newValue = oldValue;
- if (newValue->current->lastDataUpdateAt() < newItem->lastDataUpdateAt())
- newValue->current->refreshedDataAt(newItem->lastDataUpdateAt());
- } else if (oldValue->current->lastDataUpdateAt() > newItem->lastDataUpdateAt()) {
- newValue = oldValue;
+ QMutexLocker l(mutex());
+ myStatus = m_status;
+ switch (myStatus) {
+ case Status::NotStarted:
+ m_status = Status::Starting;
+ break;
+ case Status::Starting:
+ case Status::InProgress:
+ if (!m_toDo.isEmpty()) {
+ dep = m_toDo.dequeue();
+ m_inProgress.append(dep);
+ depValid = true;
+ }
+ break;
+ case Status::CallingCallbacks:
+ case Status::Done:
+ break;
+ }
+ }
+ switch (myStatus) {
+ case Status::NotStarted:
+ refreshedDataAt(QDateTime::currentDateTimeUtc());
+ doAddDependencies(self);
+ refreshedDataAt(QDateTime::currentDateTimeUtc());
+ {
+ QMutexLocker l(mutex());
+ Q_ASSERT(m_status == Status::Starting);
+ if (m_toDo.isEmpty() && m_inProgress.isEmpty())
+ myStatus = m_status = Status::CallingCallbacks;
+ else
+ myStatus = m_status = Status::InProgress;
+ }
+ if (myStatus == Status::CallingCallbacks)
+ execEnd(self);
+ break;
+ case Status::Starting:
+ case Status::InProgress:
+ if (depValid) {
+ refreshedDataAt(QDateTime::currentDateTimeUtc());
+ auto envPtr = self.environment().ownerAs<DomEnvironment>();
+ Q_ASSERT(envPtr && "missing environment");
+ if (!dep.uri.isEmpty()) {
+ envPtr->loadModuleDependency(
+ dep.uri, dep.version,
+ [this, copiedSelf = self, dep](Path, const DomItem &, const DomItem &) {
+ // Need to explicitly copy self here since we might store this and
+ // call it later.
+ finishedLoadingDep(copiedSelf, dep);
+ },
+ self.errorHandler());
+ Q_ASSERT(dep.filePath.isEmpty() && "dependency with both uri and file");
+ } else if (!dep.filePath.isEmpty()) {
+ envPtr->loadFile(
+ FileToLoad::fromFileSystem(envPtr, dep.filePath),
+ [this, copiedSelf = self, dep](Path, const DomItem &, const DomItem &) {
+ // Need to explicitly copy self here since we might store this and
+ // call it later.
+ finishedLoadingDep(copiedSelf, dep);
+ },
+ dep.fileType, self.errorHandler());
} else {
- newValue = oldValue->makeCopy(univ.copy(oldValue));
- newValue->current = newItem;
- newValue->currentExposedAt = now;
- if (newItem->isValid()) {
- newValue->valid = newItem;
- newValue->validExposedAt = now;
- }
- it = map.insert(it, canonicalPath, newValue);
+ Q_ASSERT(false && "dependency without uri and filePath");
}
} else {
- newValue = std::make_shared<ExternalItemPair<T>>
- (newItem->isValid() ? newItem : std::shared_ptr<T>(), newItem, now, now);
- map.insert(canonicalPath, newValue);
+ addErrorLocal(DomEnvironment::myErrors().error(
+ tr("advanceLoad called but found no work, which should never happen")));
+ }
+ break;
+ case Status::CallingCallbacks:
+ case Status::Done:
+ addErrorLocal(DomEnvironment::myErrors().error(tr(
+ "advanceLoad called after work should have been done, which should never happen")));
+ break;
+ }
+}
+
+void LoadInfo::finishedLoadingDep(const DomItem &self, const Dependency &d)
+{
+ bool didRemove = false;
+ bool unexpectedState = false;
+ bool doEnd = false;
+ {
+ QMutexLocker l(mutex());
+ didRemove = m_inProgress.removeOne(d);
+ switch (m_status) {
+ case Status::NotStarted:
+ case Status::CallingCallbacks:
+ case Status::Done:
+ unexpectedState = true;
+ break;
+ case Status::Starting:
+ break;
+ case Status::InProgress:
+ if (m_toDo.isEmpty() && m_inProgress.isEmpty()) {
+ m_status = Status::CallingCallbacks;
+ doEnd = true;
+ }
+ break;
+ }
+ }
+ if (!didRemove) {
+ addErrorLocal(DomEnvironment::myErrors().error([&self](const Sink &sink) {
+ sink(u"LoadInfo::finishedLoadingDep did not find its dependency in those inProgress "
+ u"()");
+ self.dump(sink);
+ sink(u")");
+ }));
+ Q_ASSERT(false
+ && "LoadInfo::finishedLoadingDep did not find its dependency in those inProgress");
+ }
+ if (unexpectedState) {
+ addErrorLocal(DomEnvironment::myErrors().error([&self](const Sink &sink) {
+ sink(u"LoadInfo::finishedLoadingDep found an unexpected state (");
+ self.dump(sink);
+ sink(u")");
+ }));
+ Q_ASSERT(false && "LoadInfo::finishedLoadingDep did find an unexpected state");
+ }
+ if (doEnd)
+ execEnd(self);
+}
+
+void LoadInfo::execEnd(const DomItem &self)
+{
+ QList<std::function<void(Path, const DomItem &, const DomItem &)>> endCallbacks;
+ bool unexpectedState = false;
+ {
+ QMutexLocker l(mutex());
+ unexpectedState = m_status != Status::CallingCallbacks;
+ endCallbacks = m_endCallbacks;
+ m_endCallbacks.clear();
+ }
+ Q_ASSERT(!unexpectedState && "LoadInfo::execEnd found an unexpected state");
+ Path p = elementCanonicalPath();
+ DomItem el = self.path(p);
+ {
+ auto cleanup = qScopeGuard([this, p, &el] {
+ QList<std::function<void(Path, const DomItem &, const DomItem &)>> otherCallbacks;
+ bool unexpectedState2 = false;
+ {
+ QMutexLocker l(mutex());
+ unexpectedState2 = m_status != Status::CallingCallbacks;
+ m_status = Status::Done;
+ otherCallbacks = m_endCallbacks;
+ m_endCallbacks.clear();
+ }
+ Q_ASSERT(!unexpectedState2 && "LoadInfo::execEnd found an unexpected state");
+ for (auto const &cb : otherCallbacks) {
+ if (cb)
+ cb(p, el, el);
+ }
+ });
+ for (auto const &cb : endCallbacks) {
+ if (cb)
+ cb(p, el, el);
+ }
+ }
+}
+
+void LoadInfo::doAddDependencies(const DomItem &self)
+{
+ if (!elementCanonicalPath()) {
+ DomEnvironment::myErrors()
+ .error(tr("Uninitialized LoadInfo %1").arg(self.canonicalPath().toString()))
+ .handle(nullptr);
+ Q_ASSERT(false);
+ return;
+ }
+ // sychronous add of all dependencies
+ DomItem el = self.path(elementCanonicalPath());
+ if (el.internalKind() == DomType::ExternalItemInfo) {
+ DomItem currentFile = el.field(Fields::currentItem);
+ QString currentFilePath = currentFile.canonicalFilePath();
+ // do not mess with QmlFile's lazy-loading
+ if (currentFile.internalKind() != DomType::QmlFile) {
+ DomItem currentQmltypesFiles = currentFile.field(Fields::qmltypesFiles);
+ int qEnd = currentQmltypesFiles.indexes();
+ for (int i = 0; i < qEnd; ++i) {
+ DomItem qmltypesRef = currentQmltypesFiles.index(i);
+ if (const Reference *ref = qmltypesRef.as<Reference>()) {
+ Path canonicalPath = ref->referredObjectPath[2];
+ if (canonicalPath && !canonicalPath.headName().isEmpty())
+ addDependency(
+ self,
+ Dependency{ QString(), Version(), canonicalPath.headName(),
+ DomType::QmltypesFile });
+ }
+ }
+ DomItem currentQmlFiles = currentFile.field(Fields::qmlFiles);
+ currentQmlFiles.visitKeys([this, &self](const QString &, const DomItem &els) {
+ return els.visitIndexes([this, &self](const DomItem &el) {
+ if (const Reference *ref = el.as<Reference>()) {
+ Path canonicalPath = ref->referredObjectPath[2];
+ if (canonicalPath && !canonicalPath.headName().isEmpty())
+ addDependency(self,
+ Dependency{ QString(), Version(),
+ canonicalPath.headName(), DomType::QmlFile });
+ }
+ return true;
+ });
+ });
}
+ } else if (shared_ptr<ModuleIndex> elPtr = el.ownerAs<ModuleIndex>()) {
+ const auto qmldirs = elPtr->qmldirsToLoad(el);
+ for (const Path &qmldirPath : qmldirs) {
+ Path canonicalPath = qmldirPath[2];
+ if (canonicalPath && !canonicalPath.headName().isEmpty())
+ addDependency(self,
+ Dependency { QString(), Version(), canonicalPath.headName(),
+ DomType::QmldirFile });
+ }
+ QString uri = elPtr->uri();
+ addEndCallback(self, [uri, qmldirs](Path, const DomItem &, const DomItem &newV) {
+ for (const Path &p : qmldirs) {
+ DomItem qmldir = newV.path(p);
+ if (std::shared_ptr<QmldirFile> qmldirFilePtr = qmldir.ownerAs<QmldirFile>()) {
+ qmldirFilePtr->ensureInModuleIndex(qmldir, uri);
+ }
+ }
+ });
+ } else if (!el) {
+ self.addError(DomEnvironment::myErrors().error(
+ tr("Ignoring dependencies for empty (invalid) type %1")
+ .arg(domTypeToString(el.internalKind()))));
+ } else {
+ self.addError(
+ DomEnvironment::myErrors().error(tr("dependencies of %1 (%2) not yet implemented")
+ .arg(domTypeToString(el.internalKind()),
+ elementCanonicalPath().toString())));
}
- return qMakePair(oldValue, newValue);
}
-void DomUniverse::execQueue()
+void LoadInfo::addDependency(const DomItem &self, const Dependency &dep)
{
- ParsingTask t = m_queue.dequeue();
- shared_ptr<DomUniverse> topPtr = t.requestingUniverse.lock();
- if (!topPtr) {
- myErrors().error(tr("Ignoring callback for loading of %1: universe is not valid anymore").arg(t.canonicalPath)).handle();
+ bool unexpectedState = false;
+ {
+ QMutexLocker l(mutex());
+ unexpectedState = m_status != Status::Starting;
+ m_toDo.enqueue(dep);
}
- Q_ASSERT(false && "Unhandled kind in queue");
+ Q_ASSERT(!unexpectedState && "LoadInfo::addDependency found an unexpected state");
+ DomItem env = self.environment();
+ env.ownerAs<DomEnvironment>()->addWorkForLoadInfo(elementCanonicalPath());
}
/*!
\class QQmlJS::Dom::DomEnvironment
\brief Represents a consistent set of types organized in modules, it is the top level of the DOM
+
+The DomEnvironment keeps a pointer m_lastValidBase to the last used valid DomEnvironment in the
+commitToBase() method. This allows the qqmldomastcreator to commit lazily loaded dependencies to the
+valid environment used by qmlls.
*/
-ErrorGroups DomEnvironment::myErrors() {
+ErrorGroups DomEnvironment::myErrors()
+{
static ErrorGroups res = {{NewErrorGroup("Dom")}};
return res;
}
+DomType DomEnvironment::kind() const
+{
+ return kindValue;
+}
Path DomEnvironment::canonicalPath() const
{
- return Path::root(u"env");
+ return Path::Root(u"env");
}
-bool DomEnvironment::iterateDirectSubpaths(DomItem &self, function<bool (Path, DomItem &)> visitor)
+bool DomEnvironment::iterateDirectSubpaths(const DomItem &self, DirectVisitor visitor) const
{
bool cont = true;
cont = cont && DomTop::iterateDirectSubpaths(self, visitor);
DomItem univ = universe();
- cont = cont && visitor(Path::field(Fields::universe), univ);
- cont = cont && self.subDataField(Fields::options, int(options())).visit(visitor);
- DomItem baseItem = base();
- cont = cont && visitor(Path::field(Fields::base), baseItem);
- cont = cont && self.subList(List::fromQList<QString>(
- Path::field(Fields::loadPaths), loadPaths(),
- [](const DomItem &i, Path p, const QString &el){
- return i.subDataPath(p, el).item;
- })).visit(visitor);
+ cont = cont && self.dvItemField(visitor, Fields::universe, [this]() { return universe(); });
+ cont = cont && self.dvValueField(visitor, Fields::options, int(options()));
+ cont = cont && self.dvItemField(visitor, Fields::base, [this]() { return base(); });
+ cont = cont
+ && self.dvValueLazyField(visitor, Fields::loadPaths, [this]() { return loadPaths(); });
+ cont = cont && self.dvValueField(visitor, Fields::globalScopeName, globalScopeName());
+ cont = cont && self.dvItemField(visitor, Fields::globalScopeWithName, [this, &self]() {
+ return self.subMapItem(Map(
+ Path::Field(Fields::globalScopeWithName),
+ [&self, this](const DomItem &map, const QString &key) {
+ return map.copy(globalScopeWithName(self, key));
+ },
+ [&self, this](const DomItem &) { return globalScopeNames(self); },
+ QLatin1String("GlobalScope")));
+ });
+ cont = cont && self.dvItemField(visitor, Fields::qmlDirectoryWithPath, [this, &self]() {
+ return self.subMapItem(Map(
+ Path::Field(Fields::qmlDirectoryWithPath),
+ [&self, this](const DomItem &map, const QString &key) {
+ return map.copy(qmlDirectoryWithPath(self, key));
+ },
+ [&self, this](const DomItem &) { return qmlDirectoryPaths(self); },
+ QLatin1String("QmlDirectory")));
+ });
+ cont = cont && self.dvItemField(visitor, Fields::qmldirFileWithPath, [this, &self]() {
+ return self.subMapItem(Map(
+ Path::Field(Fields::qmldirFileWithPath),
+ [&self, this](const DomItem &map, const QString &key) {
+ return map.copy(qmldirFileWithPath(self, key));
+ },
+ [&self, this](const DomItem &) { return qmldirFilePaths(self); },
+ QLatin1String("QmldirFile")));
+ });
+ cont = cont && self.dvItemField(visitor, Fields::qmldirWithPath, [this, &self]() {
+ return self.subMapItem(Map(
+ Path::Field(Fields::qmldirWithPath),
+ [&self, this](const DomItem &map, const QString &key) {
+ return map.copy(qmlDirWithPath(self, key));
+ },
+ [&self, this](const DomItem &) { return qmlDirPaths(self); }, QLatin1String("Qmldir")));
+ });
+ cont = cont && self.dvItemField(visitor, Fields::qmlFileWithPath, [this, &self]() {
+ return self.subMapItem(Map(
+ Path::Field(Fields::qmlFileWithPath),
+ [&self, this](const DomItem &map, const QString &key) {
+ return map.copy(qmlFileWithPath(self, key));
+ },
+ [&self, this](const DomItem &) { return qmlFilePaths(self); }, QLatin1String("QmlFile")));
+ });
+ cont = cont && self.dvItemField(visitor, Fields::jsFileWithPath, [this, &self]() {
+ return self.subMapItem(Map(
+ Path::Field(Fields::jsFileWithPath),
+ [this](const DomItem &map, const QString &key) {
+ DomItem mapOw(map.owner());
+ return map.copy(jsFileWithPath(mapOw, key));
+ },
+ [this](const DomItem &map) {
+ DomItem mapOw = map.owner();
+ return jsFilePaths(mapOw);
+ },
+ QLatin1String("JsFile")));
+ });
+ cont = cont && self.dvItemField(visitor, Fields::qmltypesFileWithPath, [this, &self]() {
+ return self.subMapItem(Map(
+ Path::Field(Fields::qmltypesFileWithPath),
+ [this](const DomItem &map, const QString &key) {
+ DomItem mapOw = map.owner();
+ return map.copy(qmltypesFileWithPath(mapOw, key));
+ },
+ [this](const DomItem &map) {
+ DomItem mapOw = map.owner();
+ return qmltypesFilePaths(mapOw);
+ },
+ QLatin1String("QmltypesFile")));
+ });
+ cont = cont && self.dvItemField(visitor, Fields::moduleIndexWithUri, [this, &self]() {
+ return self.subMapItem(Map(
+ Path::Field(Fields::moduleIndexWithUri),
+ [this](const DomItem &map, const QString &key) {
+ return map.subMapItem(Map(
+ map.pathFromOwner().key(key),
+ [this, key](const DomItem &submap, const QString &subKey) {
+ bool ok;
+ int i = subKey.toInt(&ok);
+ if (!ok) {
+ if (subKey.isEmpty())
+ i = Version::Undefined;
+ else if (subKey.compare(u"Latest", Qt::CaseInsensitive) == 0)
+ i = Version::Latest;
+ else
+ return DomItem();
+ }
+ DomItem subMapOw = submap.owner();
+ std::shared_ptr<ModuleIndex> mIndex =
+ moduleIndexWithUri(subMapOw, key, i);
+ return submap.copy(mIndex);
+ },
+ [this, key](const DomItem &subMap) {
+ QSet<QString> res;
+ DomItem subMapOw = subMap.owner();
+ for (int mVersion :
+ moduleIndexMajorVersions(subMapOw, key, EnvLookup::Normal))
+ if (mVersion == Version::Undefined)
+ res.insert(QString());
+ else
+ res.insert(QString::number(mVersion));
+ if (!res.isEmpty())
+ res.insert(QLatin1String("Latest"));
+ return res;
+ },
+ QLatin1String("ModuleIndex")));
+ },
+ [this](const DomItem &map) {
+ DomItem mapOw = map.owner();
+ return moduleIndexUris(mapOw);
+ },
+ QLatin1String("Map<ModuleIndex>")));
+ });
+ bool loadedLoadInfo = false;
QQueue<Path> loadsWithWork;
QQueue<Path> inProgress;
int nAllLoadedCallbacks;
- {
- QMutexLocker l(mutex());
- loadsWithWork = m_loadsWithWork;
- inProgress = m_inProgress;
- nAllLoadedCallbacks = m_allLoadedCallback.length();
- }
- cont = cont && self.subList(
- List(Path::field(Fields::loadsWithWork),
- [loadsWithWork](const DomItem &list, index_type i){
- if (i >= 0 && i < loadsWithWork.length())
- return list.subDataIndex(i, loadsWithWork.at(i).toString()).item;
- else
- return DomItem();
- }, [loadsWithWork](const DomItem &){
- return index_type(loadsWithWork.length());
- }, nullptr, QLatin1String("Path"))
- ).visit(visitor);
- cont = cont && self.subDataField(Fields::nAllLoadedCallbacks, nAllLoadedCallbacks).visit(visitor);
+ auto ensureInfo = [&]() {
+ if (!loadedLoadInfo) {
+ QMutexLocker l(mutex());
+ loadedLoadInfo = true;
+ loadsWithWork = m_loadsWithWork;
+ inProgress = m_inProgress;
+ nAllLoadedCallbacks = m_allLoadedCallback.size();
+ }
+ };
+ cont = cont
+ && self.dvItemField(
+ visitor, Fields::loadsWithWork, [&ensureInfo, &self, &loadsWithWork]() {
+ ensureInfo();
+ return self.subListItem(List(
+ Path::Field(Fields::loadsWithWork),
+ [loadsWithWork](const DomItem &list, index_type i) {
+ if (i >= 0 && i < loadsWithWork.size())
+ return list.subDataItem(PathEls::Index(i),
+ loadsWithWork.at(i).toString());
+ else
+ return DomItem();
+ },
+ [loadsWithWork](const DomItem &) {
+ return index_type(loadsWithWork.size());
+ },
+ nullptr, QLatin1String("Path")));
+ });
+ cont = cont
+ && self.dvItemField(visitor, Fields::inProgress, [&self, &ensureInfo, &inProgress]() {
+ ensureInfo();
+ return self.subListItem(List(
+ Path::Field(Fields::inProgress),
+ [inProgress](const DomItem &list, index_type i) {
+ if (i >= 0 && i < inProgress.size())
+ return list.subDataItem(PathEls::Index(i),
+ inProgress.at(i).toString());
+ else
+ return DomItem();
+ },
+ [inProgress](const DomItem &) { return index_type(inProgress.size()); },
+ nullptr, QLatin1String("Path")));
+ });
+ cont = cont && self.dvItemField(visitor, Fields::loadInfo, [&self, this]() {
+ return self.subMapItem(Map(
+ Path::Field(Fields::loadInfo),
+ [this](const DomItem &map, const QString &pStr) {
+ bool hasErrors = false;
+ Path p = Path::fromString(pStr, [&hasErrors](const ErrorMessage &m) {
+ switch (m.level) {
+ case ErrorLevel::Debug:
+ case ErrorLevel::Info:
+ break;
+ case ErrorLevel::Warning:
+ case ErrorLevel::Error:
+ case ErrorLevel::Fatal:
+ hasErrors = true;
+ break;
+ }
+ });
+ if (!hasErrors)
+ return map.copy(loadInfo(p));
+ return DomItem();
+ },
+ [this](const DomItem &) {
+ QSet<QString> res;
+ const auto infoPaths = loadInfoPaths();
+ for (const Path &p : infoPaths)
+ res.insert(p.toString());
+ return res;
+ },
+ QLatin1String("LoadInfo")));
+ });
+ cont = cont && self.dvWrapField(visitor, Fields::imports, m_implicitImports);
+ cont = cont
+ && self.dvValueLazyField(visitor, Fields::nAllLoadedCallbacks,
+ [&nAllLoadedCallbacks, &ensureInfo]() {
+ ensureInfo();
+ return nAllLoadedCallbacks;
+ });
return cont;
}
-std::shared_ptr<OwningItem> DomEnvironment::doCopy(const DomItem &)
+DomItem DomEnvironment::field(const DomItem &self, QStringView name) const
+{
+ return DomTop::field(self, name);
+}
+
+std::shared_ptr<DomEnvironment> DomEnvironment::makeCopy(const DomItem &self) const
+{
+ return std::static_pointer_cast<DomEnvironment>(doCopy(self));
+}
+
+std::shared_ptr<OwningItem> DomEnvironment::doCopy(const DomItem &) const
{
shared_ptr<DomEnvironment> res;
if (m_base)
- res = std::make_shared<DomEnvironment>(m_base, m_loadPaths, m_options);
+ res = std::make_shared<DomEnvironment>(m_base, m_loadPaths, m_options,
+ m_domCreationOptions);
else
- res = std::make_shared<DomEnvironment>(m_universe, m_loadPaths, m_options);
+ res = std::make_shared<DomEnvironment>(m_loadPaths, m_options, m_domCreationOptions,
+ m_universe);
return res;
}
+void DomEnvironment::loadFile(const FileToLoad &file, const Callback &callback,
+ std::optional<DomType> fileType, const ErrorHandler &h)
+{
+ if (options() & DomEnvironment::Option::NoDependencies)
+ loadFile(file, callback, DomTop::Callback(), fileType, h);
+ else {
+ // When the file is required to be loaded with dependencies, those dependencies
+ // will be added to the "pending" queue through envCallbackForFile
+ // then those should not be forgotten to be loaded.
+ loadFile(file, DomTop::Callback(), callback, fileType, h);
+ }
+}
+
+/*!
+ \internal
+ Depending on the options, the function will be called either with loadCallback OR endCallback
+
+ Before loading the file, envCallbackForFile will be created and passed as an argument to
+ universe().loadFile(...).
+ This is a callback which will be called after the load of the file is finished. More
+ specifically when File is required to be loaded without Dependencies only loadCallback is being
+ used. Otherwise, the callback is passed as endCallback. What endCallback means is that this
+ callback will be called only at the very end, once all necessary dependencies are being loaded.
+ Management and handing of this is happening through the m_loadsWithWork.
+*/
+// TODO(QTBUG-119550) refactor this
+void DomEnvironment::loadFile(const FileToLoad &file, const Callback &loadCallback,
+ const Callback &endCallback, std::optional<DomType> fileType,
+ const ErrorHandler &h)
+{
+ DomItem self(shared_from_this());
+ if (file.canonicalPath().isEmpty()) {
+ if (!file.content() || file.content()->data.isNull()) {
+ // file's content inavailable and no path to retrieve it
+ myErrors()
+ .error(tr("Non existing path to load: '%1'").arg(file.logicalPath()))
+ .handle(h);
+ if (loadCallback)
+ loadCallback(Path(), DomItem::empty, DomItem::empty);
+ if (endCallback)
+ addAllLoadedCallback(self, [endCallback](Path, const DomItem &, const DomItem &) {
+ endCallback(Path(), DomItem::empty, DomItem::empty);
+ });
+ return;
+ } else {
+ // fallback: path invalid but file's content is already available.
+ file.canonicalPath() = file.logicalPath();
+ }
+ }
+
+ shared_ptr<ExternalItemInfoBase> oldValue, newValue;
+ const DomType fType =
+ (bool(fileType) ? (*fileType) : fileTypeForPath(self, file.canonicalPath()));
+ switch (fType) {
+ case DomType::QmlDirectory: {
+ const auto &fetchResult = fetchFileFromEnvs<QmlDirectory>(file);
+ oldValue = fetchResult.first;
+ newValue = fetchResult.second;
+ if (!newValue) {
+ const auto &loadRes = universe()->loadFile(file, fType, m_domCreationOptions);
+ addExternalItemInfo<QmlDirectory>(loadRes.currentItem,
+ getLoadCallbackFor(fType, loadCallback), endCallback);
+ return;
+ }
+ } break;
+ case DomType::QmlFile: {
+ const auto &fetchResult = fetchFileFromEnvs<QmlFile>(file);
+ oldValue = fetchResult.first;
+ newValue = fetchResult.second;
+ if (!newValue) {
+ const auto &loadRes = universe()->loadFile(file, fType, m_domCreationOptions);
+ addExternalItemInfo<QmlFile>(loadRes.currentItem,
+ getLoadCallbackFor(fType, loadCallback), endCallback);
+ return;
+ }
+ } break;
+ case DomType::QmltypesFile: {
+ const auto &fetchResult = fetchFileFromEnvs<QmltypesFile>(file);
+ oldValue = fetchResult.first;
+ newValue = fetchResult.second;
+ if (!newValue) {
+ const auto &loadRes = universe()->loadFile(file, fType, m_domCreationOptions);
+ addExternalItemInfo<QmltypesFile>(loadRes.currentItem,
+ getLoadCallbackFor(fType, loadCallback), endCallback);
+ return;
+ }
+ } break;
+ case DomType::QmldirFile: {
+ const auto &fetchResult = fetchFileFromEnvs<QmldirFile>(file);
+ oldValue = fetchResult.first;
+ newValue = fetchResult.second;
+ if (!newValue) {
+ const auto &loadRes = universe()->loadFile(file, fType, m_domCreationOptions);
+ addExternalItemInfo<QmldirFile>(loadRes.currentItem,
+ getLoadCallbackFor(fType, loadCallback), endCallback);
+ return;
+ }
+ } break;
+ case DomType::JsFile: {
+ const auto &loadRes = universe()->loadFile(file, fType, m_domCreationOptions);
+ addExternalItemInfo<JsFile>(loadRes.currentItem, getLoadCallbackFor(fType, loadCallback),
+ endCallback);
+ return;
+ } break;
+ default: {
+ myErrors().error(tr("Unexpected file to load: '%1'").arg(file.canonicalPath())).handle(h);
+ if (loadCallback)
+ loadCallback(self.canonicalPath(), DomItem::empty, DomItem::empty);
+ if (endCallback)
+ endCallback(self.canonicalPath(), DomItem::empty, DomItem::empty);
+ return;
+ } break;
+ }
+ Path p = self.copy(newValue).canonicalPath();
+ std::shared_ptr<LoadInfo> lInfo = loadInfo(p);
+ if (lInfo) {
+ if (loadCallback) {
+ DomItem oldValueObj = self.copy(oldValue);
+ DomItem newValueObj = self.copy(newValue);
+ loadCallback(p, oldValueObj, newValueObj);
+ }
+ } else {
+ self.addError(myErrors().error(tr("missing load info in ")));
+ if (loadCallback)
+ loadCallback(self.canonicalPath(), DomItem::empty, DomItem::empty);
+ }
+ if (endCallback)
+ addAllLoadedCallback(self, [p = std::move(p), endCallback](
+ const Path &, const DomItem &, const DomItem &env) {
+ DomItem el = env.path(p);
+ endCallback(p, el, el);
+ });
+}
+
+void DomEnvironment::loadModuleDependency(
+ const QString &uri, Version version,
+ const std::function<void(const Path &, const DomItem &, const DomItem &)> &callback,
+ const ErrorHandler &errorHandler)
+{
+ DomItem envItem(shared_from_this());
+ if (options() & DomEnvironment::Option::NoDependencies)
+ loadModuleDependency(envItem, uri, version, callback, nullptr, errorHandler);
+ else
+ loadModuleDependency(envItem, uri, version, nullptr, callback, errorHandler);
+}
+
+void DomEnvironment::loadModuleDependency(const DomItem &self, const QString &uri, Version v,
+ Callback loadCallback, Callback endCallback,
+ const ErrorHandler &errorHandler)
+{
+ Q_ASSERT(!uri.contains(u'/'));
+ Path p = Paths::moduleIndexPath(uri, v.majorVersion);
+ if (v.majorVersion == Version::Latest) {
+ // load both the latest .<version> directory, and the common one
+ QStringList subPathComponents = uri.split(QLatin1Char('.'));
+ int maxV = -1;
+ bool commonV = false;
+ QString lastComponent = subPathComponents.last();
+ subPathComponents.removeLast();
+ QString subPathV = subPathComponents.join(u'/');
+ QRegularExpression vRe(QRegularExpression::anchoredPattern(
+ QRegularExpression::escape(lastComponent) + QStringLiteral(u"\\.([0-9]*)")));
+ const auto lPaths = loadPaths();
+ qCDebug(QQmlJSDomImporting) << "DomEnvironment::loadModuleDependency: Searching module with"
+ " uri"
+ << uri;
+ for (const QString &path : lPaths) {
+ QDir dir(path + (subPathV.isEmpty() ? QStringLiteral(u"") : QStringLiteral(u"/"))
+ + subPathV);
+ const auto eList = dir.entryList(QDir::Dirs | QDir::NoDotAndDotDot);
+ for (const QString &dirNow : eList) {
+ auto m = vRe.match(dirNow);
+ if (m.hasMatch()) {
+ int majorV = m.captured(1).toInt();
+ if (majorV > maxV) {
+ QFileInfo fInfo(dir.canonicalPath() + QChar(u'/') + dirNow
+ + QStringLiteral(u"/qmldir"));
+ if (fInfo.isFile()) {
+ qCDebug(QQmlJSDomImporting)
+ << "Found qmldir in " << fInfo.canonicalFilePath();
+ maxV = majorV;
+ }
+ }
+ }
+ if (!commonV && dirNow == lastComponent) {
+ QFileInfo fInfo(dir.canonicalPath() + QChar(u'/') + dirNow
+ + QStringLiteral(u"/qmldir"));
+ if (fInfo.isFile()) {
+ qCDebug(QQmlJSDomImporting)
+ << "Found qmldir in " << fInfo.canonicalFilePath();
+ commonV = true;
+ }
+ }
+ }
+ }
+
+ // This decrements _separately_ for each copy of the lambda. So, what we get here is not a
+ // limit on the total number of calls but a limit on the number of calls per caller
+ // location. It gets even funnier if the callback is first called and then copied further.
+ // TODO: Is this the intended behavior?
+ int toLoad = (commonV ? 1 : 0) + ((maxV >= 0) ? 1 : 0);
+ const auto loadCallback2 = loadCallback
+ ? [p, loadCallback, toLoad](Path, const DomItem &, const DomItem &elV) mutable {
+ if (--toLoad == 0) {
+ DomItem el = elV.path(p);
+ loadCallback(p, el, el);
+ }
+ }
+ : Callback();
+
+ if (maxV >= 0)
+ loadModuleDependency(self, uri, Version(maxV, v.minorVersion), loadCallback2, nullptr);
+ if (commonV)
+ loadModuleDependency(self, uri, Version(Version::Undefined, v.minorVersion),
+ loadCallback2, nullptr);
+ else if (maxV < 0) {
+ if (uri != u"QML") {
+ const QString loadPaths = lPaths.join(u", "_s);
+ qCDebug(QQmlJSDomImporting)
+ << "DomEnvironment::loadModuleDependency: qmldir at" << (uri + u"/qmldir"_s)
+ << "was not found in " << loadPaths;
+ addErrorLocal(
+ myErrors()
+ .warning(tr("Failed to find main qmldir file for %1 %2 in %3.")
+ .arg(uri, v.stringValue(), loadPaths))
+ .handle());
+ }
+ if (loadCallback)
+ loadCallback(p, DomItem::empty, DomItem::empty);
+ }
+ } else {
+ std::shared_ptr<ModuleIndex> mIndex = moduleIndexWithUri(
+ self, uri, v.majorVersion, EnvLookup::Normal, Changeable::Writable, errorHandler);
+ std::shared_ptr<LoadInfo> lInfo = loadInfo(p);
+ if (lInfo) {
+ DomItem lInfoObj = self.copy(lInfo);
+ lInfo->addEndCallback(lInfoObj, loadCallback);
+ } else {
+ addErrorLocal(
+ myErrors().warning(tr("Missing loadInfo for %1").arg(p.toString())).handle());
+ if (loadCallback)
+ loadCallback(p, DomItem::empty, DomItem::empty);
+ }
+ }
+ if (endCallback) {
+ addAllLoadedCallback(self, [p = std::move(p), endCallback = std::move(endCallback)](
+ Path, const DomItem &, const DomItem &env) {
+ DomItem el = env.path(p);
+ endCallback(p, el, el);
+ });
+ }
+}
+
+void DomEnvironment::loadBuiltins(const Callback &callback, const ErrorHandler &h)
+{
+ QString builtinsName = QLatin1String("builtins.qmltypes");
+ const auto lPaths = loadPaths();
+ for (const QString &path : lPaths) {
+ QDir dir(path);
+ QFileInfo fInfo(dir.filePath(builtinsName));
+ if (fInfo.isFile()) {
+ loadFile(FileToLoad::fromFileSystem(shared_from_this(), fInfo.canonicalFilePath()),
+ callback);
+ return;
+ }
+ }
+ myErrors().error(tr("Could not find builtins.qmltypes file")).handle(h);
+}
+
+void DomEnvironment::removePath(const QString &path)
+{
+ QMutexLocker l(mutex());
+ auto toDelete = [path](auto it) {
+ QString p = it.key();
+ return p.startsWith(path) && (p.size() == path.size() || p.at(path.size()) == u'/');
+ };
+ m_qmlDirectoryWithPath.removeIf(toDelete);
+ m_qmldirFileWithPath.removeIf(toDelete);
+ m_qmlFileWithPath.removeIf(toDelete);
+ m_jsFileWithPath.removeIf(toDelete);
+ m_qmltypesFileWithPath.removeIf(toDelete);
+}
+
shared_ptr<DomUniverse> DomEnvironment::universe() const {
if (m_universe)
return m_universe;
@@ -364,44 +1404,839 @@ shared_ptr<DomUniverse> DomEnvironment::universe() const {
return {};
}
-DomEnvironment::DomEnvironment(shared_ptr<DomUniverse> universe, QStringList loadPaths, Options options):
- m_options(options), m_universe(universe), m_loadPaths(loadPaths)
-{}
+template<typename T>
+QSet<QString> DomEnvironment::getStrings(function_ref<QSet<QString>()> getBase,
+ const QMap<QString, T> &selfMap, EnvLookup options) const
+{
+ QSet<QString> res;
+ if (options != EnvLookup::NoBase && m_base) {
+ if (m_base)
+ res = getBase();
+ }
+ if (options != EnvLookup::BaseOnly) {
+ QMap<QString, T> map;
+ {
+ QMutexLocker l(mutex());
+ map = selfMap;
+ }
+ auto it = map.keyBegin();
+ auto end = map.keyEnd();
+ while (it != end) {
+ res += *it;
+ ++it;
+ }
+ }
+ return res;
+}
-DomEnvironment::DomEnvironment(shared_ptr<DomEnvironment> parent, QStringList loadPaths, Options options):
- m_options(options), m_base(parent), m_loadPaths(loadPaths)
-{}
+QSet<QString> DomEnvironment::moduleIndexUris(const DomItem &, EnvLookup lookup) const
+{
+ DomItem baseObj = DomItem(m_base);
+ return this->getStrings<QMap<int, std::shared_ptr<ModuleIndex>>>(
+ [this, &baseObj] { return m_base->moduleIndexUris(baseObj, EnvLookup::Normal); },
+ m_moduleIndexWithUri, lookup);
+}
-Path ExternalItemInfoBase::canonicalPath(const DomItem &self) const
+QSet<int> DomEnvironment::moduleIndexMajorVersions(const DomItem &, const QString &uri, EnvLookup lookup) const
{
- shared_ptr<ExternalOwningItem> current = currentItem();
- return current->canonicalPath(self.copy(current, current.get())).dropTail();
+ QSet<int> res;
+ if (lookup != EnvLookup::NoBase && m_base) {
+ DomItem baseObj(m_base);
+ res = m_base->moduleIndexMajorVersions(baseObj, uri, EnvLookup::Normal);
+ }
+ if (lookup != EnvLookup::BaseOnly) {
+ QMap<int, std::shared_ptr<ModuleIndex>> map;
+ {
+ QMutexLocker l(mutex());
+ map = m_moduleIndexWithUri.value(uri);
+ }
+ auto it = map.keyBegin();
+ auto end = map.keyEnd();
+ while (it != end) {
+ res += *it;
+ ++it;
+ }
+ }
+ return res;
}
-QString ExternalItemInfoBase::canonicalFilePath(const DomItem &self) const
+std::shared_ptr<ModuleIndex> DomEnvironment::lookupModuleInEnv(const QString &uri, int majorVersion) const
{
- shared_ptr<ExternalOwningItem> current = currentItem();
- return current->canonicalFilePath(self.copy(current, current.get()));
+ QMutexLocker l(mutex());
+ auto it = m_moduleIndexWithUri.find(uri);
+ if (it == m_moduleIndexWithUri.end())
+ return {}; // we haven't seen the module yet
+ if (it->empty())
+ return {}; // module contains nothing
+ if (majorVersion == Version::Latest)
+ return it->last(); // map is ordered by version, so last == Latest
+ else
+ return it->value(majorVersion); // null shared_ptr is fine if no match
+}
+
+DomEnvironment::ModuleLookupResult DomEnvironment::moduleIndexWithUriHelper(const DomItem &self, const QString &uri, int majorVersion, EnvLookup options) const
+{
+ std::shared_ptr<ModuleIndex> res;
+ if (options != EnvLookup::BaseOnly)
+ res = lookupModuleInEnv(uri, majorVersion);
+ // if there is no base, or if we should not consider it
+ // then the only result we can end up with is the module we looked up above
+ if (options == EnvLookup::NoBase || !m_base)
+ return {std::move(res), ModuleLookupResult::FromGlobal };
+ const std::shared_ptr existingMod =
+ m_base->moduleIndexWithUri(self, uri, majorVersion, options, Changeable::ReadOnly);
+ if (!res) // the only module we can find at all is the one in base (might be null, too, though)
+ return { std::move(existingMod), ModuleLookupResult::FromBase };
+ if (!existingMod) // on the other hand, if there was nothing in base, we can only return what was in the larger env
+ return {std::move(res), ModuleLookupResult::FromGlobal };
+
+ // if we have both res and existingMod, res and existingMod should be the same
+ // _unless_ we looked for the latest version. Then one might have a higher version than the other
+ // and we have to check it
+
+ if (majorVersion == Version::Latest) {
+ if (res->majorVersion() >= existingMod->majorVersion())
+ return { std::move(res), ModuleLookupResult::FromGlobal };
+ else
+ return { std::move(existingMod), ModuleLookupResult::FromBase };
+ } else {
+ // doesn't really matter which we return, but the other overload benefits from using the
+ // version from m_moduleIndexWithUri
+ return { std::move(res), ModuleLookupResult::FromGlobal };
+ }
+}
+
+std::shared_ptr<ModuleIndex> DomEnvironment::moduleIndexWithUri(
+ const DomItem &self, const QString &uri, int majorVersion, EnvLookup options,
+ Changeable changeable, const ErrorHandler &errorHandler)
+{
+ // sanity checks
+ Q_ASSERT((changeable == Changeable::ReadOnly
+ || (majorVersion >= 0 || majorVersion == Version::Undefined))
+ && "A writeable moduleIndexWithUri call should have a version (not with "
+ "Version::Latest)");
+ if (changeable == Changeable::Writable && (m_options & Option::Exported))
+ myErrors().error(tr("A mutable module was requested in a multithreaded environment")).handle(errorHandler);
+
+
+ // use the overload which does not care about changing m_moduleIndexWithUri to find a candidate
+ auto [candidate, origin] = moduleIndexWithUriHelper(self, uri, majorVersion, options);
+
+ // A ModuleIndex from m_moduleIndexWithUri can always be returned
+ if (candidate && origin == ModuleLookupResult::FromGlobal)
+ return candidate;
+
+ // If we don't want to modify anything, return the candidate that we have found (if any)
+ if (changeable == Changeable::ReadOnly)
+ return candidate;
+
+ // Else we want to create a modifyable version
+ std::shared_ptr<ModuleIndex> newModulePtr = [&, candidate = candidate](){
+ // which is a completely new module in case we don't have candidate
+ if (!candidate)
+ return std::make_shared<ModuleIndex>(uri, majorVersion);
+ // or a copy of the candidate otherwise
+ DomItem existingModObj = self.copy(candidate);
+ return candidate->makeCopy(existingModObj);
+ }();
+
+ DomItem newModule = self.copy(newModulePtr);
+ Path p = newModule.canonicalPath();
+ {
+ QMutexLocker l(mutex());
+ auto &modsNow = m_moduleIndexWithUri[uri];
+ // As we do not hold the lock for the whole operation, some other thread
+ // might have created the module already
+ if (auto it = modsNow.constFind(majorVersion); it != modsNow.cend())
+ return *it;
+ modsNow.insert(majorVersion, newModulePtr);
+ }
+ if (p) {
+ auto lInfo = std::make_shared<LoadInfo>(p);
+ addLoadInfo(self, lInfo);
+ } else {
+ myErrors()
+ .error(tr("Could not get path for newly created ModuleIndex %1 %2")
+ .arg(uri)
+ .arg(majorVersion))
+ .handle(errorHandler);
+ }
+
+ return newModulePtr;
+}
+
+std::shared_ptr<ModuleIndex> DomEnvironment::moduleIndexWithUri(const DomItem &self, const QString &uri,
+ int majorVersion,
+ EnvLookup options) const
+{
+ return moduleIndexWithUriHelper(self, uri, majorVersion, options).module;
+}
+
+std::shared_ptr<ExternalItemInfo<QmlDirectory>>
+DomEnvironment::qmlDirectoryWithPath(const DomItem &, const QString &path, EnvLookup options) const
+{
+ return lookup<QmlDirectory>(path, options);
+}
+
+QSet<QString> DomEnvironment::qmlDirectoryPaths(const DomItem &, EnvLookup options) const
+{
+ return getStrings<std::shared_ptr<ExternalItemInfo<QmlDirectory>>>(
+ [this] {
+ DomItem baseObj(m_base);
+ return m_base->qmlDirectoryPaths(baseObj, EnvLookup::Normal);
+ },
+ m_qmlDirectoryWithPath, options);
+}
+
+std::shared_ptr<ExternalItemInfo<QmldirFile>>
+DomEnvironment::qmldirFileWithPath(const DomItem &, const QString &path, EnvLookup options) const
+{
+ return lookup<QmldirFile>(path, options);
+}
+
+QSet<QString> DomEnvironment::qmldirFilePaths(const DomItem &, EnvLookup lOptions) const
+{
+ return getStrings<std::shared_ptr<ExternalItemInfo<QmldirFile>>>(
+ [this] {
+ DomItem baseObj(m_base);
+ return m_base->qmldirFilePaths(baseObj, EnvLookup::Normal);
+ },
+ m_qmldirFileWithPath, lOptions);
+}
+
+std::shared_ptr<ExternalItemInfoBase> DomEnvironment::qmlDirWithPath(const DomItem &self, const QString &path,
+ EnvLookup options) const
+{
+ if (auto qmldirFile = qmldirFileWithPath(self, path + QLatin1String("/qmldir"), options))
+ return qmldirFile;
+ return qmlDirectoryWithPath(self, path, options);
+}
+
+QSet<QString> DomEnvironment::qmlDirPaths(const DomItem &self, EnvLookup options) const
+{
+ QSet<QString> res = qmlDirectoryPaths(self, options);
+ const auto qmldirFiles = qmldirFilePaths(self, options);
+ for (const QString &p : qmldirFiles) {
+ if (p.endsWith(u"/qmldir")) {
+ res.insert(p.left(p.size() - 7));
+ } else {
+ myErrors()
+ .warning(tr("Unexpected path not ending with qmldir in qmldirFilePaths: %1")
+ .arg(p))
+ .handle();
+ }
+ }
+ return res;
+}
+
+std::shared_ptr<ExternalItemInfo<QmlFile>>
+DomEnvironment::qmlFileWithPath(const DomItem &, const QString &path, EnvLookup options) const
+{
+ return lookup<QmlFile>(path, options);
+}
+
+QSet<QString> DomEnvironment::qmlFilePaths(const DomItem &, EnvLookup lookup) const
+{
+ return getStrings<std::shared_ptr<ExternalItemInfo<QmlFile>>>(
+ [this] {
+ DomItem baseObj(m_base);
+ return m_base->qmlFilePaths(baseObj, EnvLookup::Normal);
+ },
+ m_qmlFileWithPath, lookup);
+}
+
+std::shared_ptr<ExternalItemInfo<JsFile>>
+DomEnvironment::jsFileWithPath(const DomItem &, const QString &path, EnvLookup options) const
+{
+ return lookup<JsFile>(path, options);
+}
+
+QSet<QString> DomEnvironment::jsFilePaths(const DomItem &, EnvLookup lookup) const
+{
+ return getStrings<std::shared_ptr<ExternalItemInfo<JsFile>>>(
+ [this] {
+ DomItem baseObj(m_base);
+ return m_base->jsFilePaths(baseObj, EnvLookup::Normal);
+ },
+ m_jsFileWithPath, lookup);
+}
+
+std::shared_ptr<ExternalItemInfo<QmltypesFile>>
+DomEnvironment::qmltypesFileWithPath(const DomItem &, const QString &path, EnvLookup options) const
+{
+ return lookup<QmltypesFile>(path, options);
+}
+
+QSet<QString> DomEnvironment::qmltypesFilePaths(const DomItem &, EnvLookup lookup) const
+{
+ return getStrings<std::shared_ptr<ExternalItemInfo<QmltypesFile>>>(
+ [this] {
+ DomItem baseObj(m_base);
+ return m_base->qmltypesFilePaths(baseObj, EnvLookup::Normal);
+ },
+ m_qmltypesFileWithPath, lookup);
+}
+
+std::shared_ptr<ExternalItemInfo<GlobalScope>>
+DomEnvironment::globalScopeWithName(const DomItem &, const QString &name,
+ EnvLookup lookupOptions) const
+{
+ return lookup<GlobalScope>(name, lookupOptions);
+}
+
+std::shared_ptr<ExternalItemInfo<GlobalScope>>
+DomEnvironment::ensureGlobalScopeWithName(const DomItem &self, const QString &name, EnvLookup lookupOptions)
+{
+ if (auto current = globalScopeWithName(self, name, lookupOptions))
+ return current;
+ if (auto u = universe()) {
+ if (auto newVal = u->ensureGlobalScopeWithName(name)) {
+ if (auto current = newVal->current) {
+ DomItem currentObj = DomItem(u).copy(current);
+ auto newScope = current->makeCopy(currentObj);
+ auto newCopy = std::make_shared<ExternalItemInfo<GlobalScope>>(
+ newScope);
+ QMutexLocker l(mutex());
+ if (auto oldVal = m_globalScopeWithName.value(name))
+ return oldVal;
+ m_globalScopeWithName.insert(name, newCopy);
+ return newCopy;
+ }
+ }
+ }
+ Q_ASSERT_X(false, "DomEnvironment::ensureGlobalScopeWithName", "could not ensure globalScope");
+ return {};
+}
+
+QSet<QString> DomEnvironment::globalScopeNames(const DomItem &, EnvLookup lookupOptions) const
+{
+ QSet<QString> res;
+ if (lookupOptions != EnvLookup::NoBase && m_base) {
+ if (m_base) {
+ DomItem baseObj(m_base);
+ res = m_base->globalScopeNames(baseObj, EnvLookup::Normal);
+ }
+ }
+ if (lookupOptions != EnvLookup::BaseOnly) {
+ QMap<QString, std::shared_ptr<ExternalItemInfo<GlobalScope>>> map;
+ {
+ QMutexLocker l(mutex());
+ map = m_globalScopeWithName;
+ }
+ auto it = map.keyBegin();
+ auto end = map.keyEnd();
+ while (it != end) {
+ res += *it;
+ ++it;
+ }
+ }
+ return res;
+}
+
+/*!
+ \internal
+ Depending on the creation options, this function adds LoadInfo of the provided path
+*/
+void DomEnvironment::addDependenciesToLoad(const Path &path)
+{
+ if (options() & Option::NoDependencies) {
+ return;
+ }
+ Q_ASSERT(path);
+ const auto loadInfo = std::make_shared<LoadInfo>(path);
+ return addLoadInfo(DomItem(shared_from_this()), loadInfo);
+}
+
+/*!
+ \internal
+ Enqueues path to the m_loadsWithWork (queue of the pending "load" jobs).
+ In simpler words, schedule the load of the dependencies of the path from loadInfo.
+*/
+void DomEnvironment::addLoadInfo(const DomItem &self, const std::shared_ptr<LoadInfo> &loadInfo)
+{
+ if (!loadInfo)
+ return;
+ Path p = loadInfo->elementCanonicalPath();
+ bool addWork = loadInfo->status() != LoadInfo::Status::Done;
+ std::shared_ptr<LoadInfo> oldVal;
+ {
+ QMutexLocker l(mutex());
+ oldVal = m_loadInfos.value(p);
+ m_loadInfos.insert(p, loadInfo);
+ if (addWork)
+ m_loadsWithWork.enqueue(p);
+ }
+ if (oldVal && oldVal->status() != LoadInfo::Status::Done) {
+ self.addError(myErrors()
+ .error(tr("addLoadinfo replaces unfinished load info for %1")
+ .arg(p.toString()))
+ .handle());
+ }
+}
+
+std::shared_ptr<LoadInfo> DomEnvironment::loadInfo(const Path &path) const
+{
+ QMutexLocker l(mutex());
+ return m_loadInfos.value(path);
+}
+
+QHash<Path, std::shared_ptr<LoadInfo>> DomEnvironment::loadInfos() const
+{
+ QMutexLocker l(mutex());
+ return m_loadInfos;
+}
+
+QList<Path> DomEnvironment::loadInfoPaths() const
+{
+ auto lInfos = loadInfos();
+ return lInfos.keys();
}
-Path ExternalItemInfoBase::pathFromOwner(const DomItem &self) const
+DomItem::Callback DomEnvironment::getLoadCallbackFor(DomType fileType, const Callback &loadCallback)
+{
+ if (fileType == DomType::QmltypesFile) {
+ return [loadCallback](const Path &p, const DomItem &oldV, const DomItem &newV) {
+ DomItem newFile = newV.field(Fields::currentItem);
+ if (std::shared_ptr<QmltypesFile> newFilePtr = newFile.ownerAs<QmltypesFile>())
+ newFilePtr->ensureInModuleIndex(newFile);
+ if (loadCallback)
+ loadCallback(p, oldV, newV);
+ };
+ }
+ return loadCallback;
+}
+
+DomEnvironment::DomEnvironment(const QStringList &loadPaths, Options options,
+ DomCreationOptions domCreationOptions,
+ const shared_ptr<DomUniverse> &universe)
+ : m_options(options),
+ m_universe(DomUniverse::guaranteeUniverse(universe)),
+ m_loadPaths(loadPaths),
+ m_implicitImports(defaultImplicitImports()),
+ m_domCreationOptions(domCreationOptions)
+
+{
+}
+
+/*!
+\internal
+Do not call this method inside of DomEnvironment's constructor! It requires weak_from_this() that
+only works after the constructor call finished.
+*/
+DomEnvironment::SemanticAnalysis &DomEnvironment::semanticAnalysis()
+{
+ if (m_semanticAnalysis)
+ return *m_semanticAnalysis;
+
+ Q_ASSERT(domCreationOptions().testFlag(DomCreationOption::WithSemanticAnalysis));
+
+ m_semanticAnalysis = SemanticAnalysis(m_loadPaths);
+ const auto &importer = m_semanticAnalysis->m_importer;
+
+ importer->setImportVisitor([self = weak_from_this(), base = m_base](
+ QQmlJS::AST::Node *rootNode, QQmlJSImporter *importer,
+ const QQmlJSImporter::ImportVisitorPrerequisites &p) {
+ Q_UNUSED(rootNode);
+ Q_UNUSED(importer);
+
+ // support the "commitToBase" workflow, which does changes in a temporary
+ // DomEnvironment. if the current DomEnvironment is temporary (e.g. the weak pointer is
+ // null), then we assume that the current DomEnvironment was committed to base and then
+ // destructed. Use the base DomEnvironment instead.
+ std::shared_ptr<DomEnvironment> envPtr;
+ if (auto ptr = self.lock()) {
+ envPtr = ptr;
+ } else {
+ envPtr = base;
+ }
+ // populate QML File if from implicit import directory
+ // use the version in DomEnvironment and do *not* load from disk.
+ auto it = envPtr->m_qmlFileWithPath.constFind(p.m_logger->fileName());
+ if (it == envPtr->m_qmlFileWithPath.constEnd()) {
+ qCDebug(domLog) << "Import visitor tried to lazily load file \""
+ << p.m_logger->fileName()
+ << "\", but that file was not found in the DomEnvironment. Was this "
+ "file not discovered by the Dom's dependency loading mechanism?";
+ return;
+ }
+ const DomItem qmlFile = it.value()->currentItem(DomItem(envPtr));
+ envPtr->populateFromQmlFile(MutableDomItem(qmlFile));
+ });
+
+ return *m_semanticAnalysis;
+}
+
+DomEnvironment::SemanticAnalysis::SemanticAnalysis(const QStringList &loadPaths)
+ : m_mapper(
+ std::make_shared<QQmlJSResourceFileMapper>(resourceFilesFromBuildFolders(loadPaths))),
+ m_importer(std::make_shared<QQmlJSImporter>(loadPaths, m_mapper.get(), true))
+{
+}
+
+void DomEnvironment::SemanticAnalysis::setLoadPaths(const QStringList &loadPaths)
+{
+ // TODO: maybe also update the build paths in m_mapper?
+ m_importer->setImportPaths(loadPaths);
+}
+
+std::shared_ptr<DomEnvironment> DomEnvironment::create(const QStringList &loadPaths,
+ Options options,
+ DomCreationOptions domCreationOptions,
+ const DomItem &universe)
+{
+ std::shared_ptr<DomUniverse> universePtr = universe.ownerAs<DomUniverse>();
+ return std::make_shared<DomEnvironment>(loadPaths, options, domCreationOptions, universePtr);
+}
+
+DomEnvironment::DomEnvironment(const shared_ptr<DomEnvironment> &parent,
+ const QStringList &loadPaths, Options options,
+ DomCreationOptions domCreationOptions)
+ : m_options(options),
+ m_base(parent),
+ m_loadPaths(loadPaths),
+ m_implicitImports(defaultImplicitImports()),
+ m_domCreationOptions(domCreationOptions)
+{
+}
+
+void DomEnvironment::addQmlFile(const std::shared_ptr<QmlFile> &file, AddOption options)
+{
+ if (domCreationOptions().testFlag(DomCreationOption::WithSemanticAnalysis)) {
+ const QQmlJSScope::Ptr &handle =
+ semanticAnalysis().m_importer->importFile(file->canonicalFilePath());
+ file->setHandleForPopulation(handle);
+ }
+ addExternalItem(file, file->canonicalFilePath(), options);
+}
+
+void DomEnvironment::addQmlDirectory(const std::shared_ptr<QmlDirectory> &file, AddOption options)
+{
+ addExternalItem(file, file->canonicalFilePath(), options);
+}
+
+void DomEnvironment::addQmldirFile(const std::shared_ptr<QmldirFile> &file, AddOption options)
+{
+ addExternalItem(file, file->canonicalFilePath(), options);
+}
+
+void DomEnvironment::addQmltypesFile(const std::shared_ptr<QmltypesFile> &file, AddOption options)
+{
+ addExternalItem(file, file->canonicalFilePath(), options);
+}
+
+void DomEnvironment::addJsFile(const std::shared_ptr<JsFile> &file, AddOption options)
+{
+ addExternalItem(file, file->canonicalFilePath(), options);
+}
+
+void DomEnvironment::addGlobalScope(const std::shared_ptr<GlobalScope> &scope, AddOption options)
+{
+ addExternalItem(scope, scope->name(), options);
+}
+
+bool DomEnvironment::commitToBase(
+ const DomItem &self, const shared_ptr<DomEnvironment> &validEnvPtr)
+{
+ if (!base())
+ return false;
+ QMap<QString, QMap<int, std::shared_ptr<ModuleIndex>>> my_moduleIndexWithUri;
+ QMap<QString, std::shared_ptr<ExternalItemInfo<GlobalScope>>> my_globalScopeWithName;
+ QMap<QString, std::shared_ptr<ExternalItemInfo<QmlDirectory>>> my_qmlDirectoryWithPath;
+ QMap<QString, std::shared_ptr<ExternalItemInfo<QmldirFile>>> my_qmldirFileWithPath;
+ QMap<QString, std::shared_ptr<ExternalItemInfo<QmlFile>>> my_qmlFileWithPath;
+ QMap<QString, std::shared_ptr<ExternalItemInfo<JsFile>>> my_jsFileWithPath;
+ QMap<QString, std::shared_ptr<ExternalItemInfo<QmltypesFile>>> my_qmltypesFileWithPath;
+ QHash<Path, std::shared_ptr<LoadInfo>> my_loadInfos;
+ {
+ QMutexLocker l(mutex());
+ my_moduleIndexWithUri = m_moduleIndexWithUri;
+ my_globalScopeWithName = m_globalScopeWithName;
+ my_qmlDirectoryWithPath = m_qmlDirectoryWithPath;
+ my_qmldirFileWithPath = m_qmldirFileWithPath;
+ my_qmlFileWithPath = m_qmlFileWithPath;
+ my_jsFileWithPath = m_jsFileWithPath;
+ my_qmltypesFileWithPath = m_qmltypesFileWithPath;
+ my_loadInfos = m_loadInfos;
+ }
+ {
+ QMutexLocker lBase(base()->mutex()); // be more careful about makeCopy calls with lock?
+ m_base->m_semanticAnalysis = m_semanticAnalysis;
+ m_base->m_globalScopeWithName.insert(my_globalScopeWithName);
+ m_base->m_qmlDirectoryWithPath.insert(my_qmlDirectoryWithPath);
+ m_base->m_qmldirFileWithPath.insert(my_qmldirFileWithPath);
+ m_base->m_qmlFileWithPath.insert(my_qmlFileWithPath);
+ m_base->m_jsFileWithPath.insert(my_jsFileWithPath);
+ m_base->m_qmltypesFileWithPath.insert(my_qmltypesFileWithPath);
+ m_base->m_loadInfos.insert(my_loadInfos);
+ {
+ auto it = my_moduleIndexWithUri.cbegin();
+ auto end = my_moduleIndexWithUri.cend();
+ while (it != end) {
+ QMap<int, shared_ptr<ModuleIndex>> &myVersions =
+ m_base->m_moduleIndexWithUri[it.key()];
+ auto it2 = it.value().cbegin();
+ auto end2 = it.value().cend();
+ while (it2 != end2) {
+ auto oldV = myVersions.value(it2.key());
+ DomItem it2Obj = self.copy(it2.value());
+ auto newV = it2.value()->makeCopy(it2Obj);
+ newV->mergeWith(oldV);
+ myVersions.insert(it2.key(), newV);
+ ++it2;
+ }
+ ++it;
+ }
+ }
+ }
+ if (validEnvPtr)
+ m_lastValidBase = validEnvPtr;
+ if (m_lastValidBase) {
+ QMutexLocker lValid(
+ m_lastValidBase->mutex()); // be more careful about makeCopy calls with lock?
+ m_base->m_semanticAnalysis = m_semanticAnalysis;
+ m_lastValidBase->m_globalScopeWithName.insert(my_globalScopeWithName);
+ m_lastValidBase->m_qmlDirectoryWithPath.insert(my_qmlDirectoryWithPath);
+ m_lastValidBase->m_qmldirFileWithPath.insert(my_qmldirFileWithPath);
+ for (auto it = my_qmlFileWithPath.cbegin(), end = my_qmlFileWithPath.cend(); it != end;
+ ++it) {
+ if (it.value() && it.value()->current && it.value()->current->isValid())
+ m_lastValidBase->m_qmlFileWithPath.insert(it.key(), it.value());
+ }
+ for (auto it = my_jsFileWithPath.cbegin(), end = my_jsFileWithPath.cend(); it != end;
+ ++it) {
+ if (it.value() && it.value()->current && it.value()->current->isValid())
+ m_lastValidBase->m_jsFileWithPath.insert(it.key(), it.value());
+ }
+ m_lastValidBase->m_qmltypesFileWithPath.insert(my_qmltypesFileWithPath);
+ m_lastValidBase->m_loadInfos.insert(my_loadInfos);
+ for (auto it = my_moduleIndexWithUri.cbegin(), end = my_moduleIndexWithUri.cend();
+ it != end; ++it) {
+ QMap<int, shared_ptr<ModuleIndex>> &myVersions =
+ m_lastValidBase->m_moduleIndexWithUri[it.key()];
+ for (auto it2 = it.value().cbegin(), end2 = it.value().cend(); it2 != end2; ++it2) {
+ auto oldV = myVersions.value(it2.key());
+ DomItem it2Obj = self.copy(it2.value());
+ auto newV = it2.value()->makeCopy(it2Obj);
+ newV->mergeWith(oldV);
+ myVersions.insert(it2.key(), newV);
+ }
+ }
+ }
+ return true;
+}
+
+void DomEnvironment::loadPendingDependencies()
+{
+ DomItem self(shared_from_this());
+ while (true) {
+ Path elToDo;
+ std::shared_ptr<LoadInfo> loadInfo;
+ {
+ QMutexLocker l(mutex());
+ if (m_loadsWithWork.isEmpty())
+ break;
+ elToDo = m_loadsWithWork.dequeue();
+ m_inProgress.append(elToDo);
+ loadInfo = m_loadInfos.value(elToDo);
+ }
+ if (loadInfo) {
+ auto cleanup = qScopeGuard([this, &elToDo, &self] {
+ QList<Callback> endCallbacks;
+ {
+ QMutexLocker l(mutex());
+ m_inProgress.removeOne(elToDo);
+ if (m_inProgress.isEmpty() && m_loadsWithWork.isEmpty()) {
+ endCallbacks = m_allLoadedCallback;
+ m_allLoadedCallback.clear();
+ }
+ }
+ for (const Callback &cb : std::as_const(endCallbacks))
+ cb(self.canonicalPath(), self, self);
+ });
+ DomItem loadInfoObj = self.copy(loadInfo);
+ loadInfo->advanceLoad(loadInfoObj);
+ } else {
+ self.addError(myErrors().error(u"DomEnvironment::loadPendingDependencies could not "
+ u"find loadInfo listed in m_loadsWithWork"));
+ {
+ QMutexLocker l(mutex());
+ m_inProgress.removeOne(elToDo);
+ }
+ Q_ASSERT(false
+ && "DomEnvironment::loadPendingDependencies could not find loadInfo listed in "
+ "m_loadsWithWork");
+ }
+ }
+}
+
+bool DomEnvironment::finishLoadingDependencies(int waitMSec)
+{
+ bool hasPendingLoads = true;
+ QDateTime endTime = QDateTime::currentDateTimeUtc().addMSecs(waitMSec);
+ for (int i = 0; i < waitMSec / 10 + 2; ++i) {
+ loadPendingDependencies();
+ auto lInfos = loadInfos();
+ auto it = lInfos.cbegin();
+ auto end = lInfos.cend();
+ hasPendingLoads = false;
+ while (it != end) {
+ if (*it && (*it)->status() != LoadInfo::Status::Done)
+ hasPendingLoads = true;
+ }
+ if (!hasPendingLoads)
+ break;
+ auto missing = QDateTime::currentDateTimeUtc().msecsTo(endTime);
+ if (missing < 0)
+ break;
+ if (missing > 100)
+ missing = 100;
+#if QT_FEATURE_thread
+ QThread::msleep(missing);
+#endif
+ }
+ return !hasPendingLoads;
+}
+
+void DomEnvironment::addWorkForLoadInfo(const Path &elementCanonicalPath)
+{
+ QMutexLocker l(mutex());
+ m_loadsWithWork.enqueue(elementCanonicalPath);
+}
+
+DomEnvironment::Options DomEnvironment::options() const
+{
+ return m_options;
+}
+
+std::shared_ptr<DomEnvironment> DomEnvironment::base() const
+{
+ return m_base;
+}
+
+void DomEnvironment::setLoadPaths(const QStringList &v)
+{
+ QMutexLocker l(mutex());
+ m_loadPaths = v;
+
+ if (m_semanticAnalysis)
+ m_semanticAnalysis->setLoadPaths(v);
+}
+
+QStringList DomEnvironment::loadPaths() const
+{
+ QMutexLocker l(mutex());
+ return m_loadPaths;
+}
+
+QStringList DomEnvironment::qmldirFiles() const
+{
+ QMutexLocker l(mutex());
+ return m_qmldirFileWithPath.keys();
+}
+
+QString DomEnvironment::globalScopeName() const
+{
+ return m_globalScopeName;
+}
+
+QList<Import> DomEnvironment::defaultImplicitImports()
+{
+ return QList<Import>({ Import::fromUriString(u"QML"_s, Version(1, 0)),
+ Import(QmlUri::fromUriString(u"QtQml"_s), Version(6, 0)) });
+}
+
+QList<Import> DomEnvironment::implicitImports() const
+{
+ return m_implicitImports;
+}
+
+void DomEnvironment::addAllLoadedCallback(const DomItem &self, DomTop::Callback c)
+{
+ if (c) {
+ bool immediate = false;
+ {
+ QMutexLocker l(mutex());
+ if (m_loadsWithWork.isEmpty() && m_inProgress.isEmpty())
+ immediate = true;
+ else
+ m_allLoadedCallback.append(c);
+ }
+ if (immediate)
+ c(Path(), self, self);
+ }
+}
+
+void DomEnvironment::clearReferenceCache()
+{
+ m_referenceCache.clear();
+}
+
+void DomEnvironment::populateFromQmlFile(MutableDomItem &&qmlFile)
+{
+ if (std::shared_ptr<QmlFile> qmlFilePtr = qmlFile.ownerAs<QmlFile>()) {
+ QQmlJSLogger logger; // TODO
+ // the logger filename is used to populate the QQmlJSScope filepath.
+ logger.setFileName(qmlFile.canonicalFilePath());
+
+ auto setupFile = [&qmlFilePtr, &qmlFile, this](auto &&visitor) {
+ Q_UNUSED(this); // note: integrity requires "this" to be in the capture list, while
+ // other compilers complain about "this" being unused in the lambda
+ AST::Node::accept(qmlFilePtr->ast(), visitor);
+ CommentCollector collector(qmlFile);
+ collector.collectComments();
+ };
+
+ if (m_domCreationOptions.testFlag(DomCreationOption::WithSemanticAnalysis)) {
+ auto &analysis = semanticAnalysis();
+ auto scope = analysis.m_importer->importFile(qmlFile.canonicalFilePath());
+ auto v = std::make_unique<QQmlDomAstCreatorWithQQmlJSScope>(scope, qmlFile, &logger,
+ analysis.m_importer.get());
+ v->enableLoadFileLazily(true);
+ v->enableScriptExpressions(m_domCreationOptions.testFlag(DomCreationOption::WithScriptExpressions));
+
+ setupFile(v.get());
+
+ auto typeResolver =
+ std::make_shared<QQmlJSTypeResolver>(analysis.m_importer.get());
+ typeResolver->init(&v->scopeCreator(), nullptr);
+ qmlFilePtr->setTypeResolverWithDependencies(typeResolver,
+ { analysis.m_importer, analysis.m_mapper });
+ } else {
+ auto v = std::make_unique<QQmlDomAstCreator>(qmlFile);
+ v->enableScriptExpressions(
+ m_domCreationOptions.testFlag(DomCreationOption::WithScriptExpressions));
+
+ setupFile(v.get());
+ }
+ } else {
+ qCWarning(domLog) << "populateQmlFile called on non qmlFile";
+ return;
+ }
+}
+
+QString ExternalItemInfoBase::canonicalFilePath(const DomItem &self) const
{
shared_ptr<ExternalOwningItem> current = currentItem();
- return current->pathFromOwner(self.copy(current, current.get())).dropTail();
+ DomItem currentObj = currentItem(self);
+ return current->canonicalFilePath(currentObj);
}
-bool ExternalItemInfoBase::iterateDirectSubpaths(DomItem &self, function<bool (Path, DomItem &)> visitor)
+bool ExternalItemInfoBase::iterateDirectSubpaths(const DomItem &self, DirectVisitor visitor) const
{
- if (!self.subDataField(Fields::currentRevision, currentRevision(self)).visit(visitor))
+ if (!self.dvValueLazyField(visitor, Fields::currentRevision,
+ [this, &self]() { return currentRevision(self); }))
return false;
- if (!self.subDataField(Fields::lastRevision, lastRevision(self)).visit(visitor))
+ if (!self.dvValueLazyField(visitor, Fields::lastRevision,
+ [this, &self]() { return lastRevision(self); }))
return false;
- if (!self.subDataField(Fields::lastValidRevision, QCborValue(lastValidRevision(self))).visit(visitor))
+ if (!self.dvValueLazyField(visitor, Fields::lastValidRevision,
+ [this, &self]() { return lastValidRevision(self); }))
return false;
- DomItem cItem = self.copy(currentItem(), currentItem().get());
- if (!visitor(Path::field(Fields::currentItem), cItem))
+ if (!visitor(PathEls::Field(Fields::currentItem),
+ [&self, this]() { return currentItem(self); }))
return false;
- if (!self.subDataField(Fields::currentExposedAt, QCborValue(currentExposedAt())).visit(visitor))
+ if (!self.dvValueLazyField(visitor, Fields::currentExposedAt,
+ [this]() { return currentExposedAt(); }))
return false;
return true;
}
@@ -425,16 +2260,10 @@ int ExternalItemInfoBase::lastValidRevision(const DomItem &self) const
return static_cast<int>(lastValidValue.value().toInteger(0));
}
-QString ExternalItemPairBase::canonicalFilePath(const DomItem &self) const
-{
- shared_ptr<ExternalOwningItem> current = currentItem();
- return current->canonicalFilePath(self.copy(current, current.get()));
-}
-
-Path ExternalItemPairBase::pathFromOwner(const DomItem &self) const
+QString ExternalItemPairBase::canonicalFilePath(const DomItem &) const
{
shared_ptr<ExternalOwningItem> current = currentItem();
- return current->pathFromOwner(self.copy(current, current.get())).dropTail();
+ return current->canonicalFilePath();
}
Path ExternalItemPairBase::canonicalPath(const DomItem &) const
@@ -443,19 +2272,19 @@ Path ExternalItemPairBase::canonicalPath(const DomItem &) const
return current->canonicalPath().dropTail();
}
-bool ExternalItemPairBase::iterateDirectSubpaths(DomItem &self, function<bool (Path, DomItem &)> visitor)
+bool ExternalItemPairBase::iterateDirectSubpaths(const DomItem &self, DirectVisitor visitor) const
{
- if (!self.subDataField(Fields::currentIsValid, currentIsValid()).visit(visitor))
+ if (!self.dvValueLazyField(visitor, Fields::currentIsValid,
+ [this]() { return currentIsValid(); }))
return false;
- DomItem vItem = self.copy(validItem(), validItem().get());
- if (!visitor(Path::field(Fields::validItem), vItem))
+ if (!visitor(PathEls::Field(Fields::validItem), [this, &self]() { return validItem(self); }))
return false;
- DomItem cItem = self.copy(currentItem(), currentItem().get());
- if (!visitor(Path::field(Fields::currentItem), cItem))
+ if (!visitor(PathEls::Field(Fields::currentItem),
+ [this, &self]() { return currentItem(self); }))
return false;
- if (!self.subDataField(Fields::validExposedAt, QCborValue(validExposedAt)).visit(visitor))
+ if (!self.dvValueField(visitor, Fields::validExposedAt, validExposedAt))
return false;
- if (!self.subDataField(Fields::currentExposedAt, QCborValue(currentExposedAt)).visit(visitor))
+ if (!self.dvValueField(visitor, Fields::currentExposedAt, currentExposedAt))
return false;
return true;
}
@@ -465,7 +2294,59 @@ bool ExternalItemPairBase::currentIsValid() const
return currentItem() == validItem();
}
+RefCacheEntry RefCacheEntry::forPath(const DomItem &el, const Path &canonicalPath)
+{
+ DomItem env = el.environment();
+ std::shared_ptr<DomEnvironment> envPtr = env.ownerAs<DomEnvironment>();
+ RefCacheEntry cached;
+ if (envPtr) {
+ QMutexLocker l(envPtr->mutex());
+ cached = envPtr->m_referenceCache.value(canonicalPath, {});
+ } else {
+ qCWarning(domLog) << "No Env for reference" << canonicalPath << "from"
+ << el.internalKindStr() << el.canonicalPath();
+ Q_ASSERT(false);
+ }
+ return cached;
+}
+
+bool RefCacheEntry::addForPath(const DomItem &el, const Path &canonicalPath, const RefCacheEntry &entry,
+ AddOption addOption)
+{
+ DomItem env = el.environment();
+ std::shared_ptr<DomEnvironment> envPtr = env.ownerAs<DomEnvironment>();
+ bool didSet = false;
+ if (envPtr) {
+ QMutexLocker l(envPtr->mutex());
+ RefCacheEntry &cached = envPtr->m_referenceCache[canonicalPath];
+ switch (cached.cached) {
+ case RefCacheEntry::Cached::None:
+ cached = entry;
+ didSet = true;
+ break;
+ case RefCacheEntry::Cached::First:
+ if (addOption == AddOption::Overwrite || entry.cached == RefCacheEntry::Cached::All) {
+ cached = entry;
+ didSet = true;
+ }
+ break;
+ case RefCacheEntry::Cached::All:
+ if (addOption == AddOption::Overwrite || entry.cached == RefCacheEntry::Cached::All) {
+ cached = entry;
+ didSet = true;
+ }
+ }
+ if (cached.cached == RefCacheEntry::Cached::First && cached.canonicalPaths.isEmpty())
+ cached.cached = RefCacheEntry::Cached::All;
+ } else {
+ Q_ASSERT(false);
+ }
+ return didSet;
+}
+
} // end namespace Dom
} // end namespace QQmlJS
QT_END_NAMESPACE
+
+#include "moc_qqmldomtop_p.cpp"
diff --git a/src/qmldom/qqmldomtop_p.h b/src/qmldom/qqmldomtop_p.h
index 6145d9edd1..33a921af93 100644
--- a/src/qmldom/qqmldomtop_p.h
+++ b/src/qmldom/qqmldomtop_p.h
@@ -1,40 +1,6 @@
-/****************************************************************************
-**
-** 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$
-**/
+// Copyright (C) 2020 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+
#ifndef DOMTOP_H
#define DOMTOP_H
@@ -50,7 +16,7 @@
//
#include "qqmldomitem_p.h"
-
+#include "qqmldomelements_p.h"
#include "qqmldomexternalitems_p.h"
#include <QtCore/QQueue>
@@ -61,75 +27,64 @@
#include <QtCore/QCborMap>
#include <memory>
+#include <optional>
QT_BEGIN_NAMESPACE
+using namespace Qt::Literals::StringLiterals;
+
namespace QQmlJS {
namespace Dom {
-class QMLDOM_EXPORT ParsingTask {
- Q_GADGET
-public:
- QCborMap toCbor() const {
- return QCborMap(
- {{ QString::fromUtf16(Fields::requestedAt), QCborValue(requestedAt)},
- { QString::fromUtf16(Fields::loadOptions), int(loadOptions)},
- { QString::fromUtf16(Fields::kind), int(kind)},
- { QString::fromUtf16(Fields::canonicalPath), canonicalPath},
- { QString::fromUtf16(Fields::logicalPath), logicalPath},
- { QString::fromUtf16(Fields::contents), contents},
- { QString::fromUtf16(Fields::contentsDate), QCborValue(contentsDate)},
- { QString::fromUtf16(Fields::hasCallback), bool(callback)}});
- }
-
- QDateTime requestedAt;
- LoadOptions loadOptions;
- DomType kind;
- QString canonicalPath;
- QString logicalPath;
- QString contents;
- QDateTime contentsDate;
- std::weak_ptr<DomUniverse> requestingUniverse; // make it a shared_ptr?
- function<void(Path, DomItem, DomItem)> callback;
-};
-
class QMLDOM_EXPORT ExternalItemPairBase: public OwningItem { // all access should have the lock of the DomUniverse containing this
Q_DECLARE_TR_FUNCTIONS(ExternalItemPairBase);
public:
constexpr static DomType kindValue = DomType::ExternalItemPair;
- DomType kind() const override { return kindValue; }
- ExternalItemPairBase(QDateTime validExposedAt = QDateTime::fromMSecsSinceEpoch(0),
- QDateTime currentExposedAt = QDateTime::fromMSecsSinceEpoch(0),
- int derivedFrom=0, QDateTime lastDataUpdateAt = QDateTime::fromMSecsSinceEpoch(0)):
- OwningItem(derivedFrom, lastDataUpdateAt), validExposedAt(validExposedAt), currentExposedAt(currentExposedAt)
+ DomType kind() const final override { return kindValue; }
+ ExternalItemPairBase(
+ const QDateTime &validExposedAt = QDateTime::fromMSecsSinceEpoch(0, QTimeZone::UTC),
+ const QDateTime &currentExposedAt = QDateTime::fromMSecsSinceEpoch(0, QTimeZone::UTC),
+ int derivedFrom = 0,
+ const QDateTime &lastDataUpdateAt = QDateTime::fromMSecsSinceEpoch(0, QTimeZone::UTC))
+ : OwningItem(derivedFrom, lastDataUpdateAt),
+ validExposedAt(validExposedAt),
+ currentExposedAt(currentExposedAt)
{}
ExternalItemPairBase(const ExternalItemPairBase &o):
OwningItem(o), validExposedAt(o.validExposedAt), currentExposedAt(o.currentExposedAt)
{}
virtual std::shared_ptr<ExternalOwningItem> validItem() const = 0;
+ virtual DomItem validItem(const DomItem &self) const = 0;
virtual std::shared_ptr<ExternalOwningItem> currentItem() const = 0;
+ virtual DomItem currentItem(const DomItem &self) const = 0;
- QString canonicalFilePath(const DomItem &) const override;
- Path pathFromOwner(const DomItem &self) const override;
- Path canonicalPath(const DomItem &self) const override;
- bool iterateDirectSubpaths(DomItem &self, function<bool(Path, DomItem &)>) override;
+ QString canonicalFilePath(const DomItem &) const final override;
+ Path canonicalPath(const DomItem &self) const final override;
+ bool iterateDirectSubpaths(const DomItem &self, DirectVisitor) const final override;
+ DomItem field(const DomItem &self, QStringView name) const final override
+ {
+ return OwningItem::field(self, name);
+ }
bool currentIsValid() const;
- std::shared_ptr<ExternalItemPairBase> makeCopy(const DomItem &self) {
+ std::shared_ptr<ExternalItemPairBase> makeCopy(const DomItem &self) const
+ {
return std::static_pointer_cast<ExternalItemPairBase>(doCopy(self));
}
- QDateTime lastDataUpdateAt() const override {
+ QDateTime lastDataUpdateAt() const final override
+ {
if (currentItem())
return currentItem()->lastDataUpdateAt();
return ExternalItemPairBase::lastDataUpdateAt();
}
- void refreshedDataAt(QDateTime tNew) override {
- return OwningItem::refreshedDataAt(tNew);
+ void refreshedDataAt(QDateTime tNew) final override
+ {
if (currentItem())
currentItem()->refreshedDataAt(tNew);
+ return OwningItem::refreshedDataAt(tNew);
}
friend class DomUniverse;
@@ -139,29 +94,37 @@ public:
};
template<class T>
-class QMLDOM_EXPORT ExternalItemPair: public ExternalItemPairBase { // all access should have the lock of the DomUniverse containing this
+class QMLDOM_EXPORT ExternalItemPair final : public ExternalItemPairBase
+{ // all access should have the lock of the DomUniverse containing this
protected:
- std::shared_ptr<OwningItem> doCopy(const DomItem &) override {
+ std::shared_ptr<OwningItem> doCopy(const DomItem &) const override
+ {
return std::make_shared<ExternalItemPair>(*this);
}
public:
+ constexpr static DomType kindValue = DomType::ExternalItemPair;
friend class DomUniverse;
- ExternalItemPair(std::shared_ptr<T> valid = {}, std::shared_ptr<T> current = {},
- QDateTime validExposedAt = QDateTime::fromMSecsSinceEpoch(0),
- QDateTime currentExposedAt = QDateTime::fromMSecsSinceEpoch(0),
- int derivedFrom = 0, QDateTime lastDataUpdateAt = QDateTime::fromMSecsSinceEpoch(0)):
- ExternalItemPairBase(validExposedAt, currentExposedAt, derivedFrom, lastDataUpdateAt),
- valid(valid), current(current)
+ ExternalItemPair(
+ const std::shared_ptr<T> &valid = {}, const std::shared_ptr<T> &current = {},
+ const QDateTime &validExposedAt = QDateTime::fromMSecsSinceEpoch(0, QTimeZone::UTC),
+ const QDateTime &currentExposedAt = QDateTime::fromMSecsSinceEpoch(0, QTimeZone::UTC),
+ int derivedFrom = 0,
+ const QDateTime &lastDataUpdateAt = QDateTime::fromMSecsSinceEpoch(0, QTimeZone::UTC))
+ : ExternalItemPairBase(validExposedAt, currentExposedAt, derivedFrom, lastDataUpdateAt),
+ valid(valid),
+ current(current)
{}
ExternalItemPair(const ExternalItemPair &o):
ExternalItemPairBase(o), valid(o.valid), current(o.current)
{
- QMutexLocker l(mutex());
}
std::shared_ptr<ExternalOwningItem> validItem() const override { return valid; }
+ DomItem validItem(const DomItem &self) const override { return self.copy(valid); }
std::shared_ptr<ExternalOwningItem> currentItem() const override { return current; }
- std::shared_ptr<ExternalItemPair> makeCopy(const DomItem &self) {
+ DomItem currentItem(const DomItem &self) const override { return self.copy(current); }
+ std::shared_ptr<ExternalItemPair> makeCopy(const DomItem &self) const
+ {
return std::static_pointer_cast<ExternalItemPair>(doCopy(self));
}
@@ -171,13 +134,13 @@ public:
class QMLDOM_EXPORT DomTop: public OwningItem {
public:
- DomTop(QMap<QString, std::shared_ptr<OwningItem>> extraOwningItems = {}, int derivedFrom=0):
- OwningItem(derivedFrom), m_extraOwningItems(extraOwningItems)
+ DomTop(QMap<QString, OwnerT> extraOwningItems = {}, int derivedFrom = 0)
+ : OwningItem(derivedFrom), m_extraOwningItems(extraOwningItems)
{}
DomTop(const DomTop &o):
OwningItem(o)
{
- QMap<QString, std::shared_ptr<OwningItem>> items = o.extraOwningItems();
+ QMap<QString, OwnerT> items = o.extraOwningItems();
{
QMutexLocker l(mutex());
m_extraOwningItems = items;
@@ -187,109 +150,357 @@ public:
virtual Path canonicalPath() const = 0;
- Path pathFromOwner(const DomItem &) const override;
Path canonicalPath(const DomItem &) const override;
- DomItem containingObject(const DomItem&) const override;
- bool iterateDirectSubpaths(DomItem &self, function<bool(Path, DomItem &)>) override;
+ DomItem containingObject(const DomItem &) const override;
+ bool iterateDirectSubpaths(const DomItem &self, DirectVisitor) const override;
+ template<typename T>
+ void setExtraOwningItem(const QString &fieldName, const std::shared_ptr<T> &item)
+ {
+ QMutexLocker l(mutex());
+ if (!item)
+ m_extraOwningItems.remove(fieldName);
+ else
+ m_extraOwningItems.insert(fieldName, item);
+ }
- void setExtraOwningItem(QString fieldName, std::shared_ptr<OwningItem> item);
void clearExtraOwningItems();
- QMap<QString, std::shared_ptr<OwningItem>> extraOwningItems() const;
+ QMap<QString, OwnerT> extraOwningItems() const;
+
private:
- QMap<QString, std::shared_ptr<OwningItem>> m_extraOwningItems;
+ QMap<QString, OwnerT> m_extraOwningItems;
};
-class QMLDOM_EXPORT DomUniverse: public DomTop {
+class QMLDOM_EXPORT DomUniverse final : public DomTop,
+ public std::enable_shared_from_this<DomUniverse>
+{
+ Q_GADGET
Q_DECLARE_TR_FUNCTIONS(DomUniverse);
protected:
- std::shared_ptr<OwningItem> doCopy(const DomItem &self) override;
+ std::shared_ptr<OwningItem> doCopy(const DomItem &self) const override;
+
public:
- enum class Option{
- Default,
- SingleThreaded
- };
- Q_DECLARE_FLAGS(Options, Option);
constexpr static DomType kindValue = DomType::DomUniverse;
DomType kind() const override { return kindValue; }
static ErrorGroups myErrors();
- DomUniverse(QString universeName, Options options = Option::SingleThreaded);
+ DomUniverse(const QString &universeName);
DomUniverse(const DomUniverse &) = delete;
+ static std::shared_ptr<DomUniverse> guaranteeUniverse(const std::shared_ptr<DomUniverse> &univ);
+ static DomItem create(const QString &universeName);
Path canonicalPath() const override;
- bool iterateDirectSubpaths(DomItem &self, function<bool(Path, DomItem &)>) override;
- std::shared_ptr<DomUniverse> makeCopy(const DomItem &self) {
+ using DomTop::canonicalPath;
+ bool iterateDirectSubpaths(const DomItem &self, DirectVisitor) const override;
+ std::shared_ptr<DomUniverse> makeCopy(const DomItem &self) const
+ {
return std::static_pointer_cast<DomUniverse>(doCopy(self));
}
- void loadFile(const DomItem &self, QString filePath, QString logicalPath,
- Callback callback, LoadOptions loadOptions);
- void loadFile(const DomItem &self, QString canonicalFilePath, QString logicalPath,
- QString code, QDateTime codeDate, Callback callback, LoadOptions loadOptions);
- void execQueue();
+ // Helper structure reflecting the change in the map once loading && parsing is completed
+ // formerItem - DomItem representing value (ExternalItemPair) existing in the map before the
+ // loading && parsing. Might be empty (if didn't exist / failure) or equal to currentItem
+ // currentItem - DomItem representing current map value
+ struct LoadResult
+ {
+ DomItem formerItem;
+ DomItem currentItem;
+ };
+
+ LoadResult loadFile(const FileToLoad &file, DomType fileType,
+ DomCreationOptions creationOptions = {});
+
+ void removePath(const QString &dir);
+
+ std::shared_ptr<ExternalItemPair<GlobalScope>> globalScopeWithName(const QString &name) const
+ {
+ QMutexLocker l(mutex());
+ return m_globalScopeWithName.value(name);
+ }
+
+ std::shared_ptr<ExternalItemPair<GlobalScope>> ensureGlobalScopeWithName(const QString &name)
+ {
+ if (auto current = globalScopeWithName(name))
+ return current;
+ auto newScope = std::make_shared<GlobalScope>(name);
+ auto newValue = std::make_shared<ExternalItemPair<GlobalScope>>(
+ newScope, newScope);
+ QMutexLocker l(mutex());
+ if (auto current = m_globalScopeWithName.value(name))
+ return current;
+ m_globalScopeWithName.insert(name, newValue);
+ return newValue;
+ }
+
+ QSet<QString> globalScopeNames() const
+ {
+ QMap<QString, std::shared_ptr<ExternalItemPair<GlobalScope>>> map;
+ {
+ QMutexLocker l(mutex());
+ map = m_globalScopeWithName;
+ }
+ return QSet<QString>(map.keyBegin(), map.keyEnd());
+ }
+
+ std::shared_ptr<ExternalItemPair<QmlDirectory>> qmlDirectoryWithPath(const QString &path) const
+ {
+ QMutexLocker l(mutex());
+ return m_qmlDirectoryWithPath.value(path);
+ }
+ QSet<QString> qmlDirectoryPaths() const
+ {
+ QMap<QString, std::shared_ptr<ExternalItemPair<QmlDirectory>>> map;
+ {
+ QMutexLocker l(mutex());
+ map = m_qmlDirectoryWithPath;
+ }
+ return QSet<QString>(map.keyBegin(), map.keyEnd());
+ }
+
+ std::shared_ptr<ExternalItemPair<QmldirFile>> qmldirFileWithPath(const QString &path) const
+ {
+ QMutexLocker l(mutex());
+ return m_qmldirFileWithPath.value(path);
+ }
+ QSet<QString> qmldirFilePaths() const
+ {
+ QMap<QString, std::shared_ptr<ExternalItemPair<QmldirFile>>> map;
+ {
+ QMutexLocker l(mutex());
+ map = m_qmldirFileWithPath;
+ }
+ return QSet<QString>(map.keyBegin(), map.keyEnd());
+ }
+
+ std::shared_ptr<ExternalItemPair<QmlFile>> qmlFileWithPath(const QString &path) const
+ {
+ QMutexLocker l(mutex());
+ return m_qmlFileWithPath.value(path);
+ }
+ QSet<QString> qmlFilePaths() const
+ {
+ QMap<QString, std::shared_ptr<ExternalItemPair<QmlFile>>> map;
+ {
+ QMutexLocker l(mutex());
+ map = m_qmlFileWithPath;
+ }
+ return QSet<QString>(map.keyBegin(), map.keyEnd());
+ }
+
+ std::shared_ptr<ExternalItemPair<JsFile>> jsFileWithPath(const QString &path) const
+ {
+ QMutexLocker l(mutex());
+ return m_jsFileWithPath.value(path);
+ }
+ QSet<QString> jsFilePaths() const
+ {
+ QMap<QString, std::shared_ptr<ExternalItemPair<JsFile>>> map;
+ {
+ QMutexLocker l(mutex());
+ map = m_jsFileWithPath;
+ }
+ return QSet<QString>(map.keyBegin(), map.keyEnd());
+ }
+
+ std::shared_ptr<ExternalItemPair<QmltypesFile>> qmltypesFileWithPath(const QString &path) const
+ {
+ QMutexLocker l(mutex());
+ return m_qmltypesFileWithPath.value(path);
+ }
+ QSet<QString> qmltypesFilePaths() const
+ {
+ QMap<QString, std::shared_ptr<ExternalItemPair<QmltypesFile>>> map;
+ {
+ QMutexLocker l(mutex());
+ map = m_qmltypesFileWithPath;
+ }
+ return QSet<QString>(map.keyBegin(), map.keyEnd());
+ }
QString name() const {
return m_name;
}
- Options options() const {
- return m_options;
+
+private:
+ struct ContentWithDate
+ {
+ QString content;
+ QDateTime date;
+ };
+ // contains either Content with the timestamp when it was read or an Error
+ using ReadResult = std::variant<ContentWithDate, ErrorMessage>;
+ ReadResult readFileContent(const QString &canonicalPath) const;
+
+ LoadResult load(const ContentWithDate &codeWithDate, const FileToLoad &file, DomType fType,
+ DomCreationOptions creationOptions = {});
+
+ // contains either Content to be parsed or LoadResult if loading / parsing is not needed
+ using PreloadResult = std::variant<ContentWithDate, LoadResult>;
+ PreloadResult preload(const DomItem &univ, const FileToLoad &file, DomType fType) const;
+
+ std::shared_ptr<QmlFile> parseQmlFile(const QString &code, const FileToLoad &file,
+ const QDateTime &contentDate,
+ DomCreationOptions creationOptions);
+ std::shared_ptr<JsFile> parseJsFile(const QString &code, const FileToLoad &file,
+ const QDateTime &contentDate);
+ std::shared_ptr<ExternalItemPairBase> getPathValueOrNull(DomType fType,
+ const QString &path) const;
+ std::optional<DomItem> getItemIfMostRecent(const DomItem &univ, DomType fType,
+ const QString &path) const;
+ std::optional<DomItem> getItemIfHasSameCode(const DomItem &univ, DomType fType,
+ const QString &canonicalPath,
+ const ContentWithDate &codeWithDate) const;
+ static bool valueHasMostRecentItem(const ExternalItemPairBase *value,
+ const QDateTime &lastModified);
+ static bool valueHasSameContent(const ExternalItemPairBase *value, const QString &content);
+
+ // TODO better name / consider proper public get/set
+ template <typename T>
+ QMap<QString, std::shared_ptr<ExternalItemPair<T>>> &getMutableRefToMap()
+ {
+ Q_ASSERT(!mutex()->tryLock());
+ if constexpr (std::is_same_v<T, QmlDirectory>) {
+ return m_qmlDirectoryWithPath;
+ }
+ if constexpr (std::is_same_v<T, QmldirFile>) {
+ return m_qmldirFileWithPath;
+ }
+ if constexpr (std::is_same_v<T, QmlFile>) {
+ return m_qmlFileWithPath;
+ }
+ if constexpr (std::is_same_v<T, JsFile>) {
+ return m_jsFileWithPath;
+ }
+ if constexpr (std::is_same_v<T, QmltypesFile>) {
+ return m_qmltypesFileWithPath;
+ }
+ if constexpr (std::is_same_v<T, GlobalScope>) {
+ return m_globalScopeWithName;
+ }
+ Q_UNREACHABLE();
}
- QQueue<ParsingTask> queue() const {
- QMutexLocker l(mutex());
- return m_queue;
+
+ // Inserts or updates an entry reflecting ExternalItem in the corresponding map
+ // Returns a pair of:
+ // - current ExternalItemPair, current value in the map (might be empty, or equal to curValue)
+ // - new current ExternalItemPair, value in the map after after the execution of this function
+ template <typename T>
+ QPair<std::shared_ptr<ExternalItemPair<T>>, std::shared_ptr<ExternalItemPair<T>>>
+ insertOrUpdateEntry(std::shared_ptr<T> newItem)
+ {
+ std::shared_ptr<ExternalItemPair<T>> curValue;
+ std::shared_ptr<ExternalItemPair<T>> newCurValue;
+ QString canonicalPath = newItem->canonicalFilePath();
+ QDateTime now = QDateTime::currentDateTimeUtc();
+ {
+ QMutexLocker l(mutex());
+ auto &map = getMutableRefToMap<T>();
+ auto it = map.find(canonicalPath);
+ if (it != map.cend() && (*it) && (*it)->current) {
+ curValue = *it;
+ if (valueHasSameContent(curValue.get(), newItem->code())) {
+ // value in the map has same content as newItem, a.k.a. most recent
+ newCurValue = curValue;
+ if (newCurValue->current->lastDataUpdateAt() < newItem->lastDataUpdateAt()) {
+ // update timestamp in the current, as if its content was refreshed by
+ // NewItem
+ newCurValue->current->refreshedDataAt(newItem->lastDataUpdateAt());
+ }
+ } else if (curValue->current->lastDataUpdateAt() > newItem->lastDataUpdateAt()) {
+ // value in the map is more recent than newItem, nothing to update
+ newCurValue = curValue;
+ } else {
+ // perform update with newItem
+ curValue->current = std::move(newItem);
+ curValue->currentExposedAt = now;
+ if (curValue->current->isValid()) {
+ curValue->valid = curValue->current;
+ curValue->validExposedAt = std::move(now);
+ }
+ newCurValue = curValue;
+ }
+ } else {
+ // not found / invalid, just insert
+ newCurValue = std::make_shared<ExternalItemPair<T>>(
+ (newItem->isValid() ? newItem : std::shared_ptr<T>()), newItem, now, now);
+ map.insert(canonicalPath, newCurValue);
+ }
+ }
+ return qMakePair(curValue, newCurValue);
+ }
+
+ // Inserts or updates an entry reflecting ExternalItem in the corresponding map
+ // returns LoadResult reflecting the change made to the map
+ template <typename T>
+ LoadResult insertOrUpdateExternalItem(std::shared_ptr<T> extItem)
+ {
+ auto change = insertOrUpdateEntry<T>(std::move(extItem));
+ DomItem univ(shared_from_this());
+ return { univ.copy(change.first), univ.copy(change.second) };
}
private:
QString m_name;
- Options m_options;
- QQueue<ParsingTask> m_queue;
+ QMap<QString, std::shared_ptr<ExternalItemPair<GlobalScope>>> m_globalScopeWithName;
+ QMap<QString, std::shared_ptr<ExternalItemPair<QmlDirectory>>> m_qmlDirectoryWithPath;
+ QMap<QString, std::shared_ptr<ExternalItemPair<QmldirFile>>> m_qmldirFileWithPath;
+ QMap<QString, std::shared_ptr<ExternalItemPair<QmlFile>>> m_qmlFileWithPath;
+ QMap<QString, std::shared_ptr<ExternalItemPair<JsFile>>> m_jsFileWithPath;
+ QMap<QString, std::shared_ptr<ExternalItemPair<QmltypesFile>>> m_qmltypesFileWithPath;
};
- Q_DECLARE_OPERATORS_FOR_FLAGS(DomUniverse::Options)
-
class QMLDOM_EXPORT ExternalItemInfoBase: public OwningItem {
Q_DECLARE_TR_FUNCTIONS(ExternalItemInfoBase);
public:
constexpr static DomType kindValue = DomType::ExternalItemInfo;
- DomType kind() const override { return kindValue; }
- ExternalItemInfoBase(QDateTime currentExposedAt = QDateTime::fromMSecsSinceEpoch(0),
- int derivedFrom=0, QDateTime lastDataUpdateAt = QDateTime::fromMSecsSinceEpoch(0)):
- OwningItem(derivedFrom, lastDataUpdateAt), m_currentExposedAt(currentExposedAt)
- {}
- ExternalItemInfoBase(const ExternalItemInfoBase &o):
- OwningItem(o), m_currentExposedAt(o.currentExposedAt()),
- m_logicalFilePaths(o.logicalFilePaths())
+ DomType kind() const final override { return kindValue; }
+ ExternalItemInfoBase(
+ const Path &canonicalPath,
+ const QDateTime &currentExposedAt = QDateTime::fromMSecsSinceEpoch(0, QTimeZone::UTC),
+ int derivedFrom = 0,
+ const QDateTime &lastDataUpdateAt = QDateTime::fromMSecsSinceEpoch(0, QTimeZone::UTC))
+ : OwningItem(derivedFrom, lastDataUpdateAt),
+ m_canonicalPath(canonicalPath),
+ m_currentExposedAt(currentExposedAt)
{}
+ ExternalItemInfoBase(const ExternalItemInfoBase &o) = default;
virtual std::shared_ptr<ExternalOwningItem> currentItem() const = 0;
+ virtual DomItem currentItem(const DomItem &) const = 0;
- QString canonicalFilePath(const DomItem &) const override;
- Path canonicalPath(const DomItem &) const override;
- Path pathFromOwner(const DomItem &self) const override;
- bool iterateDirectSubpaths(DomItem &self, function<bool(Path, DomItem &)>) override;
+ QString canonicalFilePath(const DomItem &) const final override;
+ Path canonicalPath() const { return m_canonicalPath; }
+ Path canonicalPath(const DomItem &) const final override { return canonicalPath(); }
+ bool iterateDirectSubpaths(const DomItem &self, DirectVisitor) const final override;
+ DomItem field(const DomItem &self, QStringView name) const final override
+ {
+ return OwningItem::field(self, name);
+ }
int currentRevision(const DomItem &self) const;
int lastRevision(const DomItem &self) const;
int lastValidRevision(const DomItem &self) const;
- std::shared_ptr<ExternalItemInfoBase> makeCopy(const DomItem &self) {
+ std::shared_ptr<ExternalItemInfoBase> makeCopy(const DomItem &self) const
+ {
return std::static_pointer_cast<ExternalItemInfoBase>(doCopy(self));
}
- QDateTime lastDataUpdateAt() const override {
+ QDateTime lastDataUpdateAt() const final override
+ {
if (currentItem())
return currentItem()->lastDataUpdateAt();
return OwningItem::lastDataUpdateAt();
}
- void refreshedDataAt(QDateTime tNew) override {
- return OwningItem::refreshedDataAt(tNew);
+ void refreshedDataAt(QDateTime tNew) final override
+ {
if (currentItem())
currentItem()->refreshedDataAt(tNew);
+ return OwningItem::refreshedDataAt(tNew);
}
- void ensureLogicalFilePath(QString path) {
+ void ensureLogicalFilePath(const QString &path) {
QMutexLocker l(mutex());
if (!m_logicalFilePaths.contains(path))
m_logicalFilePaths.append(path);
@@ -313,51 +524,203 @@ public:
private:
friend class DomEnvironment;
+ Path m_canonicalPath;
QDateTime m_currentExposedAt;
QStringList m_logicalFilePaths;
};
-template <typename T>
-class ExternalItemInfo: public ExternalItemInfoBase {
+template<typename T>
+class ExternalItemInfo final : public ExternalItemInfoBase
+{
protected:
- std::shared_ptr<OwningItem> doCopy(const DomItem &) override {
+ std::shared_ptr<OwningItem> doCopy(const DomItem &) const override
+ {
return std::make_shared<ExternalItemInfo>(*this);
}
+
public:
- ExternalItemInfo(std::shared_ptr<T> current = std::shared_ptr<T>(),
- QDateTime currentExposedAt = QDateTime::fromMSecsSinceEpoch(0),
- int derivedFrom = 0, QDateTime lastDataUpdateAt = QDateTime::fromMSecsSinceEpoch(0)):
- ExternalItemInfoBase(currentExposedAt, derivedFrom, lastDataUpdateAt), current(current)
- {}
- ExternalItemInfo(QString canonicalPath):
- current(std::make_shared<T>(canonicalPath))
+ constexpr static DomType kindValue = DomType::ExternalItemInfo;
+ ExternalItemInfo(
+ const std::shared_ptr<T> &current = std::shared_ptr<T>(),
+ const QDateTime &currentExposedAt = QDateTime::fromMSecsSinceEpoch(0, QTimeZone::UTC),
+ int derivedFrom = 0,
+ const QDateTime &lastDataUpdateAt = QDateTime::fromMSecsSinceEpoch(0, QTimeZone::UTC))
+ : ExternalItemInfoBase(current->canonicalPath().dropTail(), currentExposedAt, derivedFrom,
+ lastDataUpdateAt),
+ current(current)
{}
+ ExternalItemInfo(const QString &canonicalPath) : current(new T(canonicalPath)) { }
ExternalItemInfo(const ExternalItemInfo &o):
ExternalItemInfoBase(o), current(o.current)
{
- QMutexLocker l(mutex());
}
- std::shared_ptr<ExternalItemInfo> makeCopy(const DomItem &self) {
+ std::shared_ptr<ExternalItemInfo> makeCopy(const DomItem &self) const
+ {
return std::static_pointer_cast<ExternalItemInfo>(doCopy(self));
}
std::shared_ptr<ExternalOwningItem> currentItem() const override {
return current;
}
+ DomItem currentItem(const DomItem &self) const override { return self.copy(current); }
std::shared_ptr<T> current;
};
+class Dependency
+{ // internal, should be cleaned, but nobody should use this...
+public:
+ bool operator==(Dependency const &o) const
+ {
+ return uri == o.uri && version.majorVersion == o.version.majorVersion
+ && version.minorVersion == o.version.minorVersion && filePath == o.filePath;
+ }
+ QString uri; // either dotted uri or file:, http: https: uri
+ Version version;
+ QString filePath; // for file deps
+ DomType fileType;
+};
+
+class QMLDOM_EXPORT LoadInfo final : public OwningItem
+{
+ Q_DECLARE_TR_FUNCTIONS(LoadInfo);
+
+protected:
+ std::shared_ptr<OwningItem> doCopy(const DomItem &self) const override;
+
+public:
+ constexpr static DomType kindValue = DomType::LoadInfo;
+ DomType kind() const override { return kindValue; }
+
+ enum class Status {
+ NotStarted, // dependencies non checked yet
+ Starting, // adding deps
+ InProgress, // waiting for all deps to be loaded
+ CallingCallbacks, // calling callbacks
+ Done // fully loaded
+ };
+
+ LoadInfo(const Path &elPath = Path(), Status status = Status::NotStarted, int nLoaded = 0,
+ int derivedFrom = 0,
+ const QDateTime &lastDataUpdateAt = QDateTime::fromMSecsSinceEpoch(0, QTimeZone::UTC))
+ : OwningItem(derivedFrom, lastDataUpdateAt),
+ m_elementCanonicalPath(elPath),
+ m_status(status),
+ m_nLoaded(nLoaded)
+ {
+ }
+ LoadInfo(const LoadInfo &o) : OwningItem(o), m_elementCanonicalPath(o.elementCanonicalPath())
+ {
+ {
+ QMutexLocker l(o.mutex());
+ m_status = o.m_status;
+ m_nLoaded = o.m_nLoaded;
+ m_toDo = o.m_toDo;
+ m_inProgress = o.m_inProgress;
+ m_endCallbacks = o.m_endCallbacks;
+ }
+ }
+
+ Path canonicalPath(const DomItem &self) const override;
+
+ bool iterateDirectSubpaths(const DomItem &self, DirectVisitor) const override;
+ std::shared_ptr<LoadInfo> makeCopy(const DomItem &self) const
+ {
+ return std::static_pointer_cast<LoadInfo>(doCopy(self));
+ }
+ void addError(const DomItem &self, ErrorMessage &&msg) override
+ {
+ self.path(elementCanonicalPath()).addError(std::move(msg));
+ }
+
+ void addEndCallback(const DomItem &self, std::function<void(Path, const DomItem &, const DomItem &)> callback);
+
+ void advanceLoad(const DomItem &self);
+ void finishedLoadingDep(const DomItem &self, const Dependency &d);
+ void execEnd(const DomItem &self);
+
+ Status status() const
+ {
+ QMutexLocker l(mutex());
+ return m_status;
+ }
+
+ int nLoaded() const
+ {
+ QMutexLocker l(mutex());
+ return m_nLoaded;
+ }
+
+ Path elementCanonicalPath() const
+ {
+ QMutexLocker l(mutex()); // we should never change this, remove lock?
+ return m_elementCanonicalPath;
+ }
+
+ int nNotDone() const
+ {
+ QMutexLocker l(mutex());
+ return m_toDo.size() + m_inProgress.size();
+ }
+
+ QList<Dependency> inProgress() const
+ {
+ QMutexLocker l(mutex());
+ return m_inProgress;
+ }
+
+ QList<Dependency> toDo() const
+ {
+ QMutexLocker l(mutex());
+ return m_toDo;
+ }
+
+ int nCallbacks() const
+ {
+ QMutexLocker l(mutex());
+ return m_endCallbacks.size();
+ }
+
+private:
+ void doAddDependencies(const DomItem &self);
+ void addDependency(const DomItem &self, const Dependency &dep);
+
+ Path m_elementCanonicalPath;
+ Status m_status;
+ int m_nLoaded;
+ QQueue<Dependency> m_toDo;
+ QList<Dependency> m_inProgress;
+ QList<std::function<void(Path, const DomItem &, const DomItem &)>> m_endCallbacks;
+};
+
enum class EnvLookup { Normal, NoBase, BaseOnly };
enum class Changeable { ReadOnly, Writable };
-class QMLDOM_EXPORT DomEnvironment: public DomTop
+class QMLDOM_EXPORT RefCacheEntry
{
+ Q_GADGET
+public:
+ enum class Cached { None, First, All };
+ Q_ENUM(Cached)
+
+ static RefCacheEntry forPath(const DomItem &el, const Path &canonicalPath);
+ static bool addForPath(const DomItem &el, const Path &canonicalPath, const RefCacheEntry &entry,
+ AddOption addOption = AddOption::KeepExisting);
+
+ Cached cached = Cached::None;
+ QList<Path> canonicalPaths;
+};
+
+class QMLDOM_EXPORT DomEnvironment final : public DomTop,
+ public std::enable_shared_from_this<DomEnvironment>
+{
+ Q_GADGET
Q_DECLARE_TR_FUNCTIONS(DomEnvironment);
protected:
- std::shared_ptr<OwningItem> doCopy(const DomItem &self) override;
+ std::shared_ptr<OwningItem> doCopy(const DomItem &self) const override;
+
public:
enum class Option {
Default = 0x0,
@@ -368,50 +731,389 @@ public:
SingleThreaded = 0x10, // do all operations in a single thread
NoDependencies = 0x20 // will not load dependencies (useful when editing)
};
+ Q_ENUM(Option)
Q_DECLARE_FLAGS(Options, Option);
static ErrorGroups myErrors();
constexpr static DomType kindValue = DomType::DomEnvironment;
- DomType kind() const override { return kindValue; }
+ DomType kind() const override;
Path canonicalPath() const override;
- bool iterateDirectSubpaths(DomItem &self, function<bool(Path, DomItem &)>) override;
- std::shared_ptr<DomEnvironment> makeCopy(const DomItem &self) {
- return std::static_pointer_cast<DomEnvironment>(doCopy(self));
- }
+ using DomTop::canonicalPath;
+ bool iterateDirectSubpaths(const DomItem &self, DirectVisitor) const override;
+ DomItem field(const DomItem &self, QStringView name) const final override;
+
+ std::shared_ptr<DomEnvironment> makeCopy(const DomItem &self) const;
+
+ void loadFile(const FileToLoad &file, const Callback &callback,
+ std::optional<DomType> fileType = std::optional<DomType>(),
+ const ErrorHandler &h = nullptr /* used only in loadPendingDependencies*/);
+ void loadBuiltins(const Callback &callback = nullptr, const ErrorHandler &h = nullptr);
+ void loadModuleDependency(const QString &uri, Version v, const Callback &callback = nullptr,
+ const ErrorHandler & = nullptr);
+
+ void removePath(const QString &path);
std::shared_ptr<DomUniverse> universe() const;
- DomEnvironment(std::shared_ptr<DomUniverse> universe, QStringList loadPaths, Options options = Option::SingleThreaded);
- DomEnvironment(std::shared_ptr<DomEnvironment> parent, QStringList loadPaths, Options options = Option::SingleThreaded);
+ QSet<QString> moduleIndexUris(const DomItem &self, EnvLookup lookup = EnvLookup::Normal) const;
+ QSet<int> moduleIndexMajorVersions(const DomItem &self, const QString &uri,
+ EnvLookup lookup = EnvLookup::Normal) const;
+ std::shared_ptr<ModuleIndex> moduleIndexWithUri(const DomItem &self, const QString &uri, int majorVersion,
+ EnvLookup lookup, Changeable changeable,
+ const ErrorHandler &errorHandler = nullptr);
+ std::shared_ptr<ModuleIndex> moduleIndexWithUri(const DomItem &self, const QString &uri, int majorVersion,
+ EnvLookup lookup = EnvLookup::Normal) const;
+ std::shared_ptr<ExternalItemInfo<QmlDirectory>>
+ qmlDirectoryWithPath(const DomItem &self, const QString &path, EnvLookup options = EnvLookup::Normal) const;
+ QSet<QString> qmlDirectoryPaths(const DomItem &self, EnvLookup options = EnvLookup::Normal) const;
+ std::shared_ptr<ExternalItemInfo<QmldirFile>>
+ qmldirFileWithPath(const DomItem &self, const QString &path, EnvLookup options = EnvLookup::Normal) const;
+ QSet<QString> qmldirFilePaths(const DomItem &self, EnvLookup options = EnvLookup::Normal) const;
+ std::shared_ptr<ExternalItemInfoBase>
+ qmlDirWithPath(const DomItem &self, const QString &path, EnvLookup options = EnvLookup::Normal) const;
+ QSet<QString> qmlDirPaths(const DomItem &self, EnvLookup options = EnvLookup::Normal) const;
+ std::shared_ptr<ExternalItemInfo<QmlFile>>
+ qmlFileWithPath(const DomItem &self, const QString &path, EnvLookup options = EnvLookup::Normal) const;
+ QSet<QString> qmlFilePaths(const DomItem &self, EnvLookup lookup = EnvLookup::Normal) const;
+ std::shared_ptr<ExternalItemInfo<JsFile>>
+ jsFileWithPath(const DomItem &self, const QString &path, EnvLookup options = EnvLookup::Normal) const;
+ QSet<QString> jsFilePaths(const DomItem &self, EnvLookup lookup = EnvLookup::Normal) const;
+ std::shared_ptr<ExternalItemInfo<QmltypesFile>>
+ qmltypesFileWithPath(const DomItem &self, const QString &path, EnvLookup options = EnvLookup::Normal) const;
+ QSet<QString> qmltypesFilePaths(const DomItem &self, EnvLookup lookup = EnvLookup::Normal) const;
+ std::shared_ptr<ExternalItemInfo<GlobalScope>>
+ globalScopeWithName(const DomItem &self, const QString &name, EnvLookup lookup = EnvLookup::Normal) const;
+ std::shared_ptr<ExternalItemInfo<GlobalScope>>
+ ensureGlobalScopeWithName(const DomItem &self, const QString &name, EnvLookup lookup = EnvLookup::Normal);
+ QSet<QString> globalScopeNames(const DomItem &self, EnvLookup lookup = EnvLookup::Normal) const;
+
+ explicit DomEnvironment(const QStringList &loadPaths, Options options = Option::SingleThreaded,
+ DomCreationOptions domCreationOptions = None,
+ const std::shared_ptr<DomUniverse> &universe = nullptr);
+ explicit DomEnvironment(const std::shared_ptr<DomEnvironment> &parent,
+ const QStringList &loadPaths, Options options = Option::SingleThreaded,
+ DomCreationOptions domCreationOptions = None);
DomEnvironment(const DomEnvironment &o) = delete;
+ static std::shared_ptr<DomEnvironment>
+ create(const QStringList &loadPaths, Options options = Option::SingleThreaded,
+ DomCreationOptions creationOptions = DomCreationOption::None,
+ const DomItem &universe = DomItem::empty);
+
+ // TODO AddOption can easily be removed later. KeepExisting option only used in one
+ // place which will be removed in https://codereview.qt-project.org/c/qt/qtdeclarative/+/523217
+ void addQmlFile(const std::shared_ptr<QmlFile> &file,
+ AddOption option = AddOption::KeepExisting);
+ void addQmlDirectory(const std::shared_ptr<QmlDirectory> &file,
+ AddOption option = AddOption::KeepExisting);
+ void addQmldirFile(const std::shared_ptr<QmldirFile> &file,
+ AddOption option = AddOption::KeepExisting);
+ void addQmltypesFile(const std::shared_ptr<QmltypesFile> &file,
+ AddOption option = AddOption::KeepExisting);
+ void addJsFile(const std::shared_ptr<JsFile> &file, AddOption option = AddOption::KeepExisting);
+ void addGlobalScope(const std::shared_ptr<GlobalScope> &file,
+ AddOption option = AddOption::KeepExisting);
+
+ bool commitToBase(
+ const DomItem &self, const std::shared_ptr<DomEnvironment> &validEnv = nullptr);
+
+ void addDependenciesToLoad(const Path &path);
+ void addLoadInfo(
+ const DomItem &self, const std::shared_ptr<LoadInfo> &loadInfo);
+ std::shared_ptr<LoadInfo> loadInfo(const Path &path) const;
+ QList<Path> loadInfoPaths() const;
+ QHash<Path, std::shared_ptr<LoadInfo>> loadInfos() const;
+ void loadPendingDependencies();
+ bool finishLoadingDependencies(int waitMSec = 30000);
+ void addWorkForLoadInfo(const Path &elementCanonicalPath);
+
+ Options options() const;
+
+ std::shared_ptr<DomEnvironment> base() const;
+
+ QStringList loadPaths() const;
+ QStringList qmldirFiles() const;
+
+ QString globalScopeName() const;
+
+ static QList<Import> defaultImplicitImports();
+ QList<Import> implicitImports() const;
+
+ void addAllLoadedCallback(const DomItem &self, Callback c);
+
+ void clearReferenceCache();
+ void setLoadPaths(const QStringList &v);
+
+ // Helper structure reflecting the change in the map once loading / fetching is completed
+ // formerItem - DomItem representing value (ExternalItemInfo) existing in the map before the
+ // loading && parsing. Might be empty (if didn't exist / failure) or equal to currentItem
+ // currentItem - DomItem representing current map value
+ struct LoadResult
+ {
+ DomItem formerItem;
+ DomItem currentItem;
+ };
+ // TODO(QTBUG-121171)
+ template <typename T>
+ LoadResult insertOrUpdateExternalItemInfo(const QString &path, std::shared_ptr<T> extItem)
+ {
+ // maybe in the next revision this all can be just substituted by the addExternalItem
+ DomItem env(shared_from_this());
+ // try to fetch from the current env.
+ if (auto curValue = lookup<T>(path, EnvLookup::NoBase)) {
+ // found in the "initial" env
+ return { env.copy(curValue), env.copy(curValue) };
+ }
+ std::shared_ptr<ExternalItemInfo<T>> newCurValue;
+ // try to fetch from the base env
+ auto valueInBase = lookup<T>(path, EnvLookup::BaseOnly);
+ if (!valueInBase) {
+ // Nothing found. Just create an externalItemInfo which will be inserted
+ newCurValue = std::make_shared<ExternalItemInfo<T>>(std::move(extItem),
+ QDateTime::currentDateTimeUtc());
+ } else {
+ // prepare updated value as a copy of the value from the Base to be inserted
+ newCurValue = valueInBase->makeCopy(env);
+ if (newCurValue->current != extItem) {
+ newCurValue->current = std::move(extItem);
+ newCurValue->setCurrentExposedAt(QDateTime::currentDateTimeUtc());
+ }
+ }
+ // Before inserting new or updated value, check one more time, if ItemInfo is already
+ // present
+ // lookup<> can't be used here because of the data-race
+ {
+ QMutexLocker l(mutex());
+ auto &map = getMutableRefToMap<T>();
+ const auto &it = map.find(path);
+ if (it != map.end())
+ return { env.copy(*it), env.copy(*it) };
+ // otherwise insert
+ map.insert(path, newCurValue);
+ }
+ return { env.copy(valueInBase), env.copy(newCurValue) };
+ }
+
+ template <typename T>
+ void addExternalItemInfo(const DomItem &newExtItem, const Callback &loadCallback,
+ const Callback &endCallback)
+ {
+ // get either Valid "file" from the ExternalItemPair or the current (wip) "file"
+ std::shared_ptr<T> newItemPtr;
+ if (options() & DomEnvironment::Option::KeepValid)
+ newItemPtr = newExtItem.field(Fields::validItem).ownerAs<T>();
+ if (!newItemPtr)
+ newItemPtr = newExtItem.field(Fields::currentItem).ownerAs<T>();
+ Q_ASSERT(newItemPtr && "envCallbackForFile reached without current file");
+
+ auto loadResult = insertOrUpdateExternalItemInfo(newExtItem.canonicalFilePath(),
+ std::move(newItemPtr));
+ Path p = loadResult.currentItem.canonicalPath();
+ {
+ auto depLoad = qScopeGuard([p, this, endCallback] {
+ addDependenciesToLoad(p);
+ // add EndCallback to the queue, which should be called once all dependencies are
+ // loaded
+ if (endCallback) {
+ DomItem env = DomItem(shared_from_this());
+ addAllLoadedCallback(
+ env, [p, endCallback](Path, const DomItem &, const DomItem &env) {
+ DomItem el = env.path(p);
+ endCallback(p, el, el);
+ });
+ }
+ });
+ // call loadCallback
+ if (loadCallback) {
+ loadCallback(p, loadResult.formerItem, loadResult.currentItem);
+ }
+ }
+ }
+ void populateFromQmlFile(MutableDomItem &&qmlFile);
+ DomCreationOptions domCreationOptions() const { return m_domCreationOptions; }
+
+private:
+ friend class RefCacheEntry;
- std::shared_ptr<ExternalItemInfo<QmlFile>> addQmlFile(std::shared_ptr<QmlFile> file);
+ void loadFile(const FileToLoad &file, const Callback &loadCallback, const Callback &endCallback,
+ std::optional<DomType> fileType = std::optional<DomType>(),
+ const ErrorHandler &h = nullptr);
- void commitToBase(const DomItem &self);
+ void loadModuleDependency(const DomItem &self, const QString &uri, Version v,
+ Callback loadCallback = nullptr, Callback endCallback = nullptr,
+ const ErrorHandler & = nullptr);
- Options options() const {
- return m_options;
+ template <typename T>
+ QSet<QString> getStrings(function_ref<QSet<QString>()> getBase, const QMap<QString, T> &selfMap,
+ EnvLookup lookup) const;
+
+ template <typename T>
+ const QMap<QString, std::shared_ptr<ExternalItemInfo<T>>> &getConstRefToMap() const
+ {
+ Q_ASSERT(!mutex()->tryLock());
+ if constexpr (std::is_same_v<T, GlobalScope>) {
+ return m_globalScopeWithName;
+ }
+ if constexpr (std::is_same_v<T, QmlDirectory>) {
+ return m_qmlDirectoryWithPath;
+ }
+ if constexpr (std::is_same_v<T, QmldirFile>) {
+ return m_qmldirFileWithPath;
+ }
+ if constexpr (std::is_same_v<T, QmlFile>) {
+ return m_qmlFileWithPath;
+ }
+ if constexpr (std::is_same_v<T, JsFile>) {
+ return m_jsFileWithPath;
+ }
+ if constexpr (std::is_same_v<T, QmltypesFile>) {
+ return m_qmltypesFileWithPath;
+ }
+ Q_UNREACHABLE();
+ }
+
+ template <typename T>
+ std::shared_ptr<ExternalItemInfo<T>> lookup(const QString &path, EnvLookup options) const
+ {
+ if (options != EnvLookup::BaseOnly) {
+ QMutexLocker l(mutex());
+ const auto &map = getConstRefToMap<T>();
+ const auto &it = map.find(path);
+ if (it != map.end())
+ return *it;
+ }
+ if (options != EnvLookup::NoBase && m_base)
+ return m_base->lookup<T>(path, options);
+ return {};
}
- std::shared_ptr<DomEnvironment> base() const {
- return m_base;
+ template <typename T>
+ QMap<QString, std::shared_ptr<ExternalItemInfo<T>>> &getMutableRefToMap()
+ {
+ Q_ASSERT(!mutex()->tryLock());
+ if constexpr (std::is_same_v<T, QmlDirectory>) {
+ return m_qmlDirectoryWithPath;
+ }
+ if constexpr (std::is_same_v<T, QmldirFile>) {
+ return m_qmldirFileWithPath;
+ }
+ if constexpr (std::is_same_v<T, QmlFile>) {
+ return m_qmlFileWithPath;
+ }
+ if constexpr (std::is_same_v<T, JsFile>) {
+ return m_jsFileWithPath;
+ }
+ if constexpr (std::is_same_v<T, QmltypesFile>) {
+ return m_qmltypesFileWithPath;
+ }
+ if constexpr (std::is_same_v<T, GlobalScope>) {
+ return m_globalScopeWithName;
+ }
+ Q_UNREACHABLE();
}
- QStringList loadPaths() const {
+ template <typename T>
+ void addExternalItem(std::shared_ptr<T> file, QString key, AddOption option)
+ {
+ if (!file)
+ return;
+
+ auto eInfo = std::make_shared<ExternalItemInfo<T>>(file, QDateTime::currentDateTimeUtc());
+ // Lookup helper can't be used here, because it introduces data-race otherwise
+ // (other modifications might happen between the lookup and the insert)
QMutexLocker l(mutex());
- return m_loadPaths;
+ auto &map = getMutableRefToMap<T>();
+ const auto &it = map.find(key);
+ if (it != map.end() && option == AddOption::KeepExisting)
+ return;
+ map.insert(key, eInfo);
}
-private:
+ using FetchResult =
+ QPair<std::shared_ptr<ExternalItemInfoBase>, std::shared_ptr<ExternalItemInfoBase>>;
+ // This function tries to get an Info object about the ExternalItem from the current env
+ // and depending on the result and options tries to fetch it from the Parent env,
+ // saving a copy with an updated timestamp
+ template <typename T>
+ FetchResult fetchFileFromEnvs(const FileToLoad &file)
+ {
+ const auto &path = file.canonicalPath();
+ // lookup only in the current env
+ if (auto value = lookup<T>(path, EnvLookup::NoBase)) {
+ return qMakePair(value, value);
+ }
+ // try to find the file in the base(parent) Env and insert if found
+ if (options() & Option::NoReload) {
+ if (auto baseV = lookup<T>(path, EnvLookup::BaseOnly)) {
+ // Watch out! QTBUG-121171
+ // It's possible between the lookup and creation of curVal, baseV && baseV->current
+ // might have changed
+ // Prepare a value to be inserted as copy of the value from Base
+ auto curV = std::make_shared<ExternalItemInfo<T>>(
+ baseV->current, QDateTime::currentDateTimeUtc(), baseV->revision(),
+ baseV->lastDataUpdateAt());
+ // Lookup one more time if the value was already inserted to the current env
+ // Lookup can't be used here because of the data-race
+ {
+ QMutexLocker l(mutex());
+ auto &map = getMutableRefToMap<T>();
+ const auto &it = map.find(path);
+ if (it != map.end())
+ return qMakePair(*it, *it);
+ // otherwise insert
+ map.insert(path, curV);
+ }
+ return qMakePair(baseV, curV);
+ }
+ }
+ return qMakePair(nullptr, nullptr);
+ }
+
+ Callback getLoadCallbackFor(DomType fileType, const Callback &loadCallback);
+
+ std::shared_ptr<ModuleIndex> lookupModuleInEnv(const QString &uri, int majorVersion) const;
+ // ModuleLookupResult contains the ModuleIndex pointer, and an indicator whether it was found
+ // in m_base or in m_moduleIndexWithUri
+ struct ModuleLookupResult {
+ enum Origin : bool {FromBase, FromGlobal};
+ std::shared_ptr<ModuleIndex> module;
+ Origin fromBase = FromGlobal;
+ };
+ // helper function used by the moduleIndexWithUri methods
+ ModuleLookupResult moduleIndexWithUriHelper(const DomItem &self, const QString &uri, int majorVersion,
+ EnvLookup lookup = EnvLookup::Normal) const;
+
const Options m_options;
const std::shared_ptr<DomEnvironment> m_base;
+ std::shared_ptr<DomEnvironment> m_lastValidBase;
const std::shared_ptr<DomUniverse> m_universe;
QStringList m_loadPaths; // paths for qml
- bool m_singleThreadedLoadInProgress = false;
+ QString m_globalScopeName;
+ QMap<QString, QMap<int, std::shared_ptr<ModuleIndex>>> m_moduleIndexWithUri;
+ QMap<QString, std::shared_ptr<ExternalItemInfo<GlobalScope>>> m_globalScopeWithName;
+ QMap<QString, std::shared_ptr<ExternalItemInfo<QmlDirectory>>> m_qmlDirectoryWithPath;
+ QMap<QString, std::shared_ptr<ExternalItemInfo<QmldirFile>>> m_qmldirFileWithPath;
+ QMap<QString, std::shared_ptr<ExternalItemInfo<QmlFile>>> m_qmlFileWithPath;
+ QMap<QString, std::shared_ptr<ExternalItemInfo<JsFile>>> m_jsFileWithPath;
+ QMap<QString, std::shared_ptr<ExternalItemInfo<QmltypesFile>>> m_qmltypesFileWithPath;
QQueue<Path> m_loadsWithWork;
QQueue<Path> m_inProgress;
+ QHash<Path, std::shared_ptr<LoadInfo>> m_loadInfos;
+ QList<Import> m_implicitImports;
QList<Callback> m_allLoadedCallback;
+ QHash<Path, RefCacheEntry> m_referenceCache;
+ DomCreationOptions m_domCreationOptions;
+
+ struct SemanticAnalysis
+ {
+ SemanticAnalysis(const QStringList &loadPaths);
+ void setLoadPaths(const QStringList &loadPaths);
+
+ std::shared_ptr<QQmlJSResourceFileMapper> m_mapper;
+ std::shared_ptr<QQmlJSImporter> m_importer;
+ };
+ std::optional<SemanticAnalysis> m_semanticAnalysis;
+ SemanticAnalysis &semanticAnalysis();
};
Q_DECLARE_OPERATORS_FOR_FLAGS(DomEnvironment::Options)
diff --git a/src/qmldom/qqmldomtypesreader.cpp b/src/qmldom/qqmldomtypesreader.cpp
new file mode 100644
index 0000000000..ae1ec7fb94
--- /dev/null
+++ b/src/qmldom/qqmldomtypesreader.cpp
@@ -0,0 +1,272 @@
+// Copyright (C) 2020 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+
+#include "qqmldomtypesreader_p.h"
+#include "qqmldomelements_p.h"
+#include "qqmldomcompare_p.h"
+#include "qqmldomfieldfilter_p.h"
+
+#include <QtQml/private/qqmljsparser_p.h>
+#include <QtQml/private/qqmljslexer_p.h>
+#include <QtQml/private/qqmljsengine_p.h>
+#include <private/qqmljstypedescriptionreader_p.h>
+
+#include <QtCore/qdir.h>
+
+QT_BEGIN_NAMESPACE
+
+namespace QQmlJS {
+namespace Dom {
+
+using namespace QQmlJS::AST;
+
+static ErrorGroups readerParseErrors()
+{
+ static ErrorGroups errs = { { NewErrorGroup("Dom"), NewErrorGroup("QmltypesFile"),
+ NewErrorGroup("Parsing") } };
+ return errs;
+}
+
+void QmltypesReader::insertProperty(
+ const QQmlJSScope::ConstPtr &jsScope, const QQmlJSMetaProperty &property,
+ QMap<int, QmlObject> &objs)
+{
+ PropertyDefinition prop;
+ prop.name = property.propertyName();
+ prop.typeName = property.typeName();
+ prop.isPointer = property.isPointer();
+ prop.isReadonly = !property.isWritable();
+ prop.isRequired = jsScope->isPropertyLocallyRequired(prop.name);
+ prop.isList = property.isList();
+ int revision = property.revision();
+ prop.isFinal = property.isFinal();
+ prop.bindable = property.bindable();
+ prop.read = property.read();
+ prop.write = property.write();
+ prop.notify = property.notify();
+
+ if (prop.name.isEmpty() || prop.typeName.isEmpty()) {
+ addError(readerParseErrors()
+ .warning(tr("Property object is missing a name or type script binding."))
+ .handle());
+ return;
+ }
+ objs[revision].addPropertyDef(prop, AddOption::KeepExisting);
+}
+
+void QmltypesReader::insertSignalOrMethod(const QQmlJSMetaMethod &metaMethod,
+ QMap<int, QmlObject> &objs)
+{
+ MethodInfo methodInfo;
+ // ### confusion between Method and Slot. Method should be removed.
+ switch (metaMethod.methodType()) {
+ case QQmlJSMetaMethodType::Method:
+ case QQmlJSMetaMethodType::Slot:
+ methodInfo.methodType = MethodInfo::MethodType::Method;
+ break;
+ case QQmlJSMetaMethodType::Signal:
+ methodInfo.methodType = MethodInfo::MethodType::Signal;
+ break;
+ default:
+ Q_UNREACHABLE();
+ }
+ auto parameters = metaMethod.parameters();
+ qsizetype nParam = parameters.size();
+ for (int i = 0; i < nParam; ++i) {
+ MethodParameter param;
+ param.name = parameters[i].name();
+ param.typeName = parameters[i].typeName();
+ methodInfo.parameters.append(param);
+ }
+ methodInfo.name = metaMethod.methodName();
+ methodInfo.typeName = metaMethod.returnTypeName();
+ int revision = metaMethod.revision();
+ methodInfo.isConstructor = metaMethod.isConstructor();
+ if (methodInfo.name.isEmpty()) {
+ addError(readerParseErrors().error(tr("Method or signal is missing a name.")).handle());
+ return;
+ }
+
+ objs[revision].addMethod(methodInfo, AddOption::KeepExisting);
+}
+
+EnumDecl QmltypesReader::enumFromMetaEnum(const QQmlJSMetaEnum &metaEnum)
+{
+ EnumDecl res;
+ res.setName(metaEnum.name());
+ res.setAlias(metaEnum.alias());
+ res.setIsFlag(metaEnum.isFlag());
+ QList<EnumItem> values;
+ int lastValue = -1;
+ for (const auto &k : metaEnum.keys()) {
+ if (metaEnum.hasValues())
+ lastValue = metaEnum.value(k);
+ else
+ ++lastValue;
+ values.append(EnumItem(k, lastValue));
+ }
+ res.setValues(values);
+ return res;
+}
+
+void QmltypesReader::insertComponent(const QQmlJSScope::ConstPtr &jsScope,
+ const QList<QQmlJSScope::Export> &exportsList)
+{
+ QmltypesComponent comp;
+ comp.setSemanticScope(jsScope);
+ QMap<int, QmlObject> objects;
+ {
+ bool hasExports = false;
+ for (const QQmlJSScope::Export &jsE : exportsList) {
+ int metaRev = jsE.version().toEncodedVersion<int>();
+ hasExports = true;
+ QmlObject object;
+ object.setSemanticScope(jsScope);
+ objects.insert(metaRev, object);
+ }
+ if (!hasExports) {
+ QmlObject object;
+ object.setSemanticScope(jsScope);
+ objects.insert(0, object);
+ }
+ }
+ bool incrementedPath = false;
+ QString prototype;
+ QString defaultPropertyName;
+ {
+ QHash<QString, QQmlJSMetaProperty> els = jsScope->ownProperties();
+ auto it = els.cbegin();
+ auto end = els.cend();
+ while (it != end) {
+ insertProperty(jsScope, it.value(), objects);
+ ++it;
+ }
+ }
+ {
+ QMultiHash<QString, QQmlJSMetaMethod> els = jsScope->ownMethods();
+ auto it = els.cbegin();
+ auto end = els.cend();
+ while (it != end) {
+ insertSignalOrMethod(it.value(), objects);
+ ++it;
+ }
+ }
+ {
+ QHash<QString, QQmlJSMetaEnum> els = jsScope->ownEnumerations();
+ auto it = els.cbegin();
+ auto end = els.cend();
+ while (it != end) {
+ comp.addEnumeration(enumFromMetaEnum(it.value()));
+ ++it;
+ }
+ }
+ comp.setFileName(jsScope->filePath());
+ comp.setName(jsScope->internalName());
+ m_currentPath = m_currentPath.key(comp.name())
+ .index(qmltypesFilePtr()->components().values(comp.name()).size());
+ incrementedPath = true;
+ prototype = jsScope->baseTypeName();
+ defaultPropertyName = jsScope->ownDefaultPropertyName();
+ comp.setInterfaceNames(jsScope->interfaceNames());
+ QString typeName = jsScope->ownAttachedTypeName();
+ comp.setAttachedTypeName(typeName);
+ if (!typeName.isEmpty())
+ comp.setAttachedTypePath(Paths::lookupCppTypePath(typeName));
+ comp.setIsSingleton(jsScope->isSingleton());
+ comp.setIsCreatable(jsScope->isCreatable());
+ comp.setIsComposite(jsScope->isComposite());
+ comp.setHasCustomParser(jsScope->hasCustomParser());
+ comp.setValueTypeName(jsScope->valueTypeName());
+ comp.setAccessSemantics(jsScope->accessSemantics());
+ comp.setExtensionTypeName(jsScope->extensionTypeName());
+ comp.setExtensionIsJavaScript(jsScope->extensionIsJavaScript());
+ comp.setExtensionIsNamespace(jsScope->extensionIsNamespace());
+ Path exportSourcePath = qmltypesFile().canonicalPath();
+ QMap<int, Path> revToPath;
+ auto it = objects.end();
+ auto begin = objects.begin();
+ int objectIndex = 0;
+ QList<int> metaRevs;
+ Path compPath = qmltypesFile()
+ .canonicalPath()
+ .field(Fields::components)
+ .key(comp.name())
+ .index(qmltypesFilePtr()->components().values(comp.name()).size());
+
+ // emit & map objs
+ while (it != begin) {
+ --it;
+ if (it.key() < 0) {
+ addError(readerParseErrors().error(
+ tr("negative meta revision %1 not supported").arg(it.key())));
+ }
+ revToPath.insert(it.key(), compPath.field(Fields::objects).index(objectIndex));
+ Path nextObjectPath = compPath.field(Fields::objects).index(++objectIndex);
+ if (it == begin) {
+ if (!prototype.isEmpty())
+ it->addPrototypePath(Paths::lookupCppTypePath(prototype));
+ it->setName(prototype);
+ } else {
+ it->addPrototypePath(nextObjectPath);
+ it->setName(comp.name() + QLatin1String("-") + QString::number(it.key()));
+ }
+ comp.addObject(*it);
+ metaRevs.append(it.key());
+ }
+ comp.setMetaRevisions(metaRevs);
+
+ // exports:
+ QList<Export> exports;
+ for (const QQmlJSScope::Export &jsE : exportsList) {
+ auto v = jsE.version();
+ int metaRev = v.toEncodedVersion<int>();
+ Export e;
+ e.uri = jsE.package();
+ e.typeName = jsE.type();
+ e.isSingleton = jsScope->isSingleton();
+ e.version = Version((v.hasMajorVersion() ? v.majorVersion() : Version::Latest),
+ (v.hasMinorVersion() ? v.minorVersion() : Version::Latest));
+ e.typePath = revToPath.value(metaRev);
+ if (!e.typePath) {
+ qCWarning(domLog) << "could not find version" << metaRev << "in" << revToPath.keys();
+ }
+ e.exportSourcePath = exportSourcePath;
+ comp.addExport(e);
+ }
+
+ if (comp.name().isEmpty()) {
+ addError(readerParseErrors()
+ .error(tr("Component definition is missing a name binding."))
+ .handle());
+ return;
+ }
+ qmltypesFilePtr()->addComponent(comp, AddOption::KeepExisting);
+ if (incrementedPath)
+ m_currentPath = m_currentPath.dropTail().dropTail();
+}
+
+bool QmltypesReader::parse()
+{
+ QQmlJSTypeDescriptionReader reader(qmltypesFilePtr()->canonicalFilePath(),
+ qmltypesFilePtr()->code());
+ QStringList dependencies;
+ QList<QQmlJSExportedScope> objects;
+ const bool isValid = reader(&objects, &dependencies);
+ for (const auto &obj : std::as_const(objects))
+ insertComponent(obj.scope, obj.exports);
+ qmltypesFilePtr()->setIsValid(isValid);
+ return isValid;
+}
+
+void QmltypesReader::addError(ErrorMessage &&message)
+{
+ if (message.file.isEmpty())
+ message.file = qmltypesFile().canonicalFilePath();
+ if (!message.path)
+ message.path = m_currentPath;
+ qmltypesFilePtr()->addErrorLocal(message.handle());
+}
+
+} // end namespace Dom
+} // end namespace QQmlJS
+QT_END_NAMESPACE
diff --git a/src/qmldom/qqmldomtypesreader_p.h b/src/qmldom/qqmldomtypesreader_p.h
new file mode 100644
index 0000000000..dda0b8f5b3
--- /dev/null
+++ b/src/qmldom/qqmldomtypesreader_p.h
@@ -0,0 +1,68 @@
+// Copyright (C) 2019 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+
+#ifndef QQMLDOMTYPESREADER_H
+#define QQMLDOMTYPESREADER_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 "qqmldomexternalitems_p.h"
+
+#include <QtQml/private/qqmljsastfwd_p.h>
+
+// for Q_DECLARE_TR_FUNCTIONS
+#include <QtCore/qcoreapplication.h>
+#include <private/qqmljsmetatypes_p.h>
+#include <private/qqmljsscope_p.h>
+
+QT_BEGIN_NAMESPACE
+
+namespace QQmlJS {
+namespace Dom {
+
+class QmltypesReader
+{
+ Q_DECLARE_TR_FUNCTIONS(TypeDescriptionReader)
+public:
+ explicit QmltypesReader(const DomItem &qmltypesFile)
+ : m_qmltypesFilePtr(qmltypesFile.ownerAs<QmltypesFile>()), m_qmltypesFile(qmltypesFile)
+ {
+ }
+
+ bool parse();
+ // static void read
+private:
+ void addError(ErrorMessage &&message);
+
+ void insertProperty(const QQmlJSScope::ConstPtr &jsScope, const QQmlJSMetaProperty &property,
+ QMap<int, QmlObject> &objs);
+ void insertSignalOrMethod(const QQmlJSMetaMethod &metaMethod, QMap<int, QmlObject> &objs);
+ void insertComponent(const QQmlJSScope::ConstPtr &jsScope,
+ const QList<QQmlJSScope::Export> &exportsList);
+ EnumDecl enumFromMetaEnum(const QQmlJSMetaEnum &metaEnum);
+
+ std::shared_ptr<QmltypesFile> qmltypesFilePtr() { return m_qmltypesFilePtr; }
+ DomItem &qmltypesFile() { return m_qmltypesFile; }
+ ErrorHandler handler()
+ {
+ return [this](const ErrorMessage &m) { this->addError(ErrorMessage(m)); };
+ }
+
+private:
+ std::shared_ptr<QmltypesFile> m_qmltypesFilePtr;
+ DomItem m_qmltypesFile;
+ Path m_currentPath;
+};
+
+} // end namespace Dom
+} // end namespace QQmlJS
+QT_END_NAMESPACE
+#endif // QQMLDOMTYPESREADER_H