aboutsummaryrefslogtreecommitdiffstats
path: root/src/qmldom/qqmldomitem.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/qmldom/qqmldomitem.cpp')
-rw-r--r--src/qmldom/qqmldomitem.cpp3642
1 files changed, 3642 insertions, 0 deletions
diff --git a/src/qmldom/qqmldomitem.cpp b/src/qmldom/qqmldomitem.cpp
new file mode 100644
index 0000000000..8e4f8acd40
--- /dev/null
+++ b/src/qmldom/qqmldomitem.cpp
@@ -0,0 +1,3642 @@
+// 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>
+#include <QtQml/private/qqmljsengine_p.h>
+#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/QtGlobal>
+#include <QtCore/QTimeZone>
+#include <optional>
+#include <type_traits>
+
+QT_BEGIN_NAMESPACE
+
+namespace QQmlJS {
+namespace Dom {
+
+Q_LOGGING_CATEGORY(writeOutLog, "qt.qmldom.writeOut", QtWarningMsg);
+Q_STATIC_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
+\class QQmljs::Dom::DomBase
+
+\brief Abstract class common to all elements of the Qml code model
+
+DomBase represents the base class common to all objects exposed by the Dom Model
+through a DomItem.
+Single inheritence is mandatory for the inline items (Empty, Map, List, ConstantData, Reference),
+so that the base pointer of the object can be used a base pointer of the superclass directly.
+
+The subclass *must* have a
+\code
+ constexpr static Kind kindValue
+\endcode
+entry with its kind to enable casting usng the DomItem::as DomItem::ownerAs templates.
+
+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
+bool iterateDirectSubpaths(const DomItem &self, function_ref<bool(Path, DomItem)>) const = 0;
+\endcode
+
+\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
+*/
+
+QMap<DomType,QString> domTypeToStringMap()
+{
+ static QMap<DomType,QString> map = [](){
+ QMetaEnum metaEnum = QMetaEnum::fromType<DomType>();
+ QMap<DomType,QString> res;
+ for (int i = 0; i < metaEnum.keyCount(); ++ i) {
+ res[DomType(metaEnum.value(i))] = QString::fromUtf8(metaEnum.key(i));
+ }
+ return res;
+ }();
+ return map;
+}
+
+QString domTypeToString(DomType k)
+{
+ QString res = domTypeToStringMap().value(k);
+ if (res.isEmpty())
+ return QString::number(int(k));
+ else
+ return res;
+}
+
+QMap<DomKind, QString> domKindToStringMap()
+{
+ 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;
+}
+
+QString domKindToString(DomKind k)
+{
+ return domKindToStringMap().value(k, QString::number(int(k)));
+}
+
+bool domTypeIsExternalItem(DomType k)
+{
+ switch (k) {
+ case DomType::QmlDirectory:
+ case DomType::JsFile:
+ case DomType::QmlFile:
+ case DomType::QmltypesFile:
+ case DomType::GlobalScope:
+ return true;
+ default:
+ return false;
+ }
+}
+
+bool domTypeIsTopItem(DomType k)
+{
+ switch (k) {
+ case DomType::DomEnvironment:
+ case DomType::DomUniverse:
+ return true;
+ default:
+ return false;
+ }
+
+}
+
+bool domTypeIsContainer(DomType k)
+{
+ switch (k) {
+ case DomType::Map:
+ case DomType::List:
+ case DomType::ListP:
+ return true;
+ default:
+ return false;
+ }
+}
+
+bool domTypeIsScope(DomType k)
+{
+ switch (k) {
+ 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;
+ }
+}
+
+QString DomBase::canonicalFilePath(const DomItem &self) const
+{
+ auto parent = containingObject(self);
+ if (parent)
+ return parent.canonicalFilePath();
+ return QString();
+}
+
+void DomBase::writeOut(const DomItem &self, OutWriter &) const
+{
+ qCWarning(writeOutLog) << "Ignoring unsupported writeOut for " << domTypeToString(kind()) << ":"
+ << self.canonicalPath();
+}
+
+ConstantData::ConstantData(const Path &pathFromOwner, const QCborValue &value, Options options)
+ : DomElement(pathFromOwner), m_value(value), m_options(options)
+{}
+
+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();
+ PathEls::PathComponent comp;
+ switch (m_options) {
+ case ConstantData::Options::MapIsMap:
+ comp = PathEls::Key(key);
+ break;
+ case ConstantData::Options::FirstMapIsFields:
+ comp = PathEls::Field(toField(key));
+ break;
+ }
+ auto val = it.value();
+ if (!self.dvValue(visitor, comp, val))
+ return false;
+ ++it;
+ }
+ return true;
+ } else if (m_value.isArray()){
+ QCborArray array = m_value.toArray();
+ auto it = array.cbegin();
+ auto end = array.cend();
+ index_type i = 0;
+ while (it != end) {
+ if (!self.dvValue(visitor, PathEls::Index(i++), *it++))
+ return false;
+ }
+ return true;
+ } else {
+ return true;
+ }
+}
+
+quintptr ConstantData::id() const
+{
+ return quintptr(0);
+}
+
+DomKind ConstantData::domKind() const
+{
+ if (m_value.isMap()) {
+ switch (m_options) {
+ case ConstantData::Options::MapIsMap:
+ return DomKind::Map;
+ case ConstantData::Options::FirstMapIsFields:
+ return DomKind::Object;
+ }
+ }
+ if (m_value.isArray())
+ return DomKind::List;
+ return DomKind::Value;
+}
+/*!
+\internal
+\class QQmlJS::Dom::DomItem
+
+\brief A value type that references any element of the Dom.
+
+This class is the central element in the Dom, it is how any element can be identfied in a uniform
+way, and provides the API to explore the Dom, and Path based operations.
+
+The DomItem (unless it is Empty) keeps a pointer to the element, and a shared pointer to its owner
+and to the DomEnvironment or DomUniverse that contains them. This means that:
+\list
+\li A DomItem always has some context: you can get the canonicalPath(), go up along it with
+ 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 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
+
+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
+pointer indirection).
+Exposed OwningItems are basically immutable, but during construction, objects can be modified.
+This will typically happen from a single thread, so there aren't locking issues, but pointers to
+inner elements might become invalid.
+In this case the use of the MutableDomItem is required.
+It does not keep any pointers to internal elements, but rather the path to them, and it resolves
+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()
+{
+ static ErrorGroups res = {{domErrorGroup}};
+ return res;
+}
+
+ErrorGroups DomItem::myResolveErrors()
+{
+ static ErrorGroups res = {{domErrorGroup, NewErrorGroup("Resolve")}};
+ return res;
+}
+
+Path DomItem::canonicalPath() const
+{
+ 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 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
+{
+ Path path = pathFromOwner();
+ if (!path)
+ path = canonicalPath();
+ Source s = path.split();
+ if (s.pathFromSource.length() > 1)
+ return containingObject().path(s.pathFromSource.dropTail());
+ return containingObject();
+}
+
+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
+{
+ 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
+{
+ DomItem res = top();
+ if (res.internalKind() == DomType::DomEnvironment)
+ return res;
+ return DomItem(); // we are in the universe, and cannot go back to the environment...
+}
+
+DomItem DomItem::universe() const
+{
+ DomItem res = top();
+ if (res.internalKind() == DomType::DomUniverse)
+ return res;
+ if (res.internalKind() == DomType::DomEnvironment)
+ return res.field(Fields::universe);
+ return DomItem(); // we should be in an empty DomItem already...
+}
+
+/*!
+ \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
+{
+ 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;
+}
+
+/*!
+ \internal
+ In the DomItem hierarchy, go 1 level up to get the direct parent.
+ */
+DomItem DomItem::directParent() const
+{
+ return goUp(1);
+}
+
+/*!
+\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
+{
+ 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 {};
+}
+
+PropertyInfo DomItem::propertyInfoWithName(const QString &name) const
+{
+ 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();
+}
+
+struct ResolveToDo {
+ DomItem item;
+ int pathIndex;
+};
+
+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;
+ if (fullPath.length() == 0)
+ fPath = path;
+ if (path.length()==0)
+ return visitor(fPath, *this);
+ 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) {
+ DomItem root = *this;
+ PathRoot contextId = path.headRoot();
+ switch (contextId) {
+ case PathRoot::Modules:
+ root = root.environment().field(Fields::moduleIndexWithUri);
+ break;
+ case PathRoot::Cpp:
+ root = root.environment()[Fields::qmltypesFileWithPath];
+ break;
+ case PathRoot::Libs:
+ root = root.environment()[Fields::plugins];
+ break;
+ case PathRoot::Top:
+ root = root.top();
+ break;
+ case PathRoot::Env:
+ root = root.environment();
+ break;
+ case PathRoot::Universe:
+ root = root.environment()[u"universe"];
+ break;
+ case PathRoot::Other:
+ myResolveErrors().error(tr("Root context %1 is not known").arg(path.headName())).handle(errorHandler);
+ return false;
+ }
+ toDos[0] = {std::move(root), 1};
+ } else {
+ toDos[0] = {*this, 0};
+ }
+ while (!toDos.isEmpty()) {
+ const ResolveToDo toDo = toDos.takeLast();
+ {
+ auto idNow = toDo.item.id();
+ if (idNow == quintptr(0) && toDo.item == *this)
+ 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.id();
+ if (idNow == quintptr() && 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;
+ auto cNow = path[iPath++];
+ switch (cNow.headKind()) {
+ case Path::Kind::Key:
+ it = it.key(cNow.headName());
+ break;
+ 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;
+ }
+ 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 = resolveRes;
+ }
+ } else {
+ it = it.field(cNow.headName()); // avoid instantiation of QString?
+ }
+ break;
+ case Path::Kind::Index:
+ it = it.index(cNow.headIndex());
+ break;
+ case Path::Kind::Empty:
+ {
+ // immediate expansion, might use extra memory, but simplifies code (no suspended evaluation)
+ Path toFind;
+ do {
+ if (iPath >= path.length()) {
+ myResolveErrors().warning(tr("Resolve with path ending with empty path, matches nothing."))
+ .handle(errorHandler);
+ branchExhausted = true; // allow and visit all?
+ break;
+ }
+ toFind = path[iPath++];
+ } while (toFind.headKind() == Path::Kind::Empty);
+ QVector<Path::Kind> validFind({Path::Kind::Key, Path::Kind::Field, Path::Kind::Field, Path::Kind::Index});
+ if (!validFind.contains(toFind.headKind())) {
+ myResolveErrors().error(tr("After an empty path only key, field or indexes are supported, not %1.").arg(toFind.toString()))
+ .handle(errorHandler);
+ branchExhausted = true; // allow and visit all?
+ return false;
+ }
+ if (!branchExhausted)
+ 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;
+ }
+ case Path::Kind::Root:
+ myResolveErrors().error(tr("Root path is supported only at the beginning, and only once, found %1 at %2 in %3")
+ .arg(cNow.toString()).arg(iPath -1).arg(path.toString())).handle(errorHandler);
+ return false;
+ case Path::Kind::Current:
+ {
+ PathCurrent current = cNow.headCurrent();
+ switch (current) {
+ case PathCurrent::Other:
+ // todo
+ case PathCurrent::Obj:
+ if (domKind() != DomKind::Object)
+ it = it.containingObject();
+ break;
+ 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().ids();
+ break;
+ case PathCurrent::Types:
+ it = it.component()[Fields::exports];
+ break;
+ case PathCurrent::LookupStrict:
+ case PathCurrent::LookupDynamic:
+ case PathCurrent::Lookup: {
+ LookupOptions opt = LookupOption::Normal;
+ if (current == PathCurrent::Lookup) {
+ 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())
+ 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;
+ }
+ 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;
+ }
+ 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;
+ }
+ 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;
+ }
+ 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 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:
+ 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:
+ if (cNow.headFilter() && !cNow.headFilter()(it))
+ branchExhausted = true;
+ break;
+ }
+ }
+ // visit the resolved path
+ if (!branchExhausted && iPath == path.length() && !visitor(fPath, it))
+ return false;
+ }
+ return true;
+}
+
+DomItem DomItem::path(const Path &p, const ErrorHandler &errorHandler) const
+{
+ if (!p)
+ return *this;
+ DomItem res;
+ resolve(p, [&res](const Path &, const DomItem &it) {
+ res = it;
+ return false;
+ }, errorHandler);
+ return res;
+}
+
+DomItem DomItem::path(const QString &p, const ErrorHandler &errorHandler) const
+{
+ return path(Path::fromString(p, errorHandler));
+}
+
+DomItem DomItem::path(QStringView p, const ErrorHandler &errorHandler) const
+{
+ return path(Path::fromString(p, errorHandler));
+}
+
+QList<QString> DomItem::fields() const
+{
+ return visitEl([this](auto &&el) { return el->fields(*this); });
+}
+
+DomItem DomItem::field(QStringView name) const
+{
+ return visitEl([this, name](auto &&el) { return el->field(*this, name); });
+}
+
+index_type DomItem::indexes() const
+{
+ return visitEl([this](auto &&el) { return el->indexes(*this); });
+}
+
+DomItem DomItem::index(index_type i) const
+{
+ return visitEl([this, i](auto &&el) { return el->index(*this, i); });
+}
+
+bool DomItem::visitIndexes(function_ref<bool(const DomItem &)> visitor) const
+{
+ // use iterateDirectSubpathsConst instead?
+ int nIndexes = indexes();
+ for (int i = 0; i < nIndexes; ++i) {
+ DomItem v = index(i);
+ if (!visitor(v))
+ return false;
+ }
+ return true;
+}
+
+QSet<QString> DomItem::keys() const
+{
+ return visitEl([this](auto &&el) { return el->keys(*this); });
+}
+
+QStringList DomItem::sortedKeys() const
+{
+ QSet<QString> ks = keys();
+ QStringList sortedKs(ks.begin(), ks.end());
+ std::sort(sortedKs.begin(), sortedKs.end());
+ return sortedKs;
+}
+
+DomItem DomItem::key(const QString &name) const
+{
+ return visitEl([this, name](auto &&el) { return el->key(*this, name); });
+}
+
+bool DomItem::visitKeys(function_ref<bool(const QString &, const DomItem &)> visitor) const
+{
+ // use iterateDirectSubpathsConst instead?
+ const QStringList keys = sortedKeys();
+ for (const QString &k : keys) {
+ DomItem v = key(k);
+ if (!visitor(k, v))
+ return false;
+ }
+ return true;
+}
+
+QList<DomItem> DomItem::values() const
+{
+ 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;
+}
+
+void DomItem::writeOutPre(OutWriter &ow) const
+{
+ 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::writeOut(OutWriter &ow) const
+{
+ writeOutPre(ow);
+ visitEl([this, &ow](auto &&el) { el->writeOut(*this, ow); });
+ writeOutPost(ow);
+}
+
+void DomItem::writeOutPost(OutWriter &ow) const
+{
+ ow.itemEnd(*this);
+}
+
+DomItem::WriteOutCheckResult DomItem::performWriteOutChecks(const DomItem &original, const DomItem &reformatted,
+ OutWriter &ow,
+ WriteOutChecks extraChecks) const
+{
+ 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;
+}
+
+/*!
+ \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;
+ }
+}
+
+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;
+}
+
+bool DomItem::hasAnnotations() const
+{
+ 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;
+}
+
+/*!
+ \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)
+{
+ Path elId = prototype.canonicalPath();
+ if (visitedRefs->contains(elId))
+ return true;
+ else
+ 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;
+}
+
+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
+{
+ // 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;
+}
+
+/*!
+ \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
+{
+ return visitScopeChain(
+ [symbolName, visitor](const DomItem &obj) {
+ return obj.visitLocalSymbolsNamed(symbolName,
+ [visitor](const DomItem &el) { return visitor(el); });
+ },
+ opts, h, visited, visitedRefs);
+}
+
+class CppTypeInfo
+{
+ 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;
+}
+
+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 (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;
+}
+
+/*!
+ \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 DomItem();
+}
+
+QList<DomItem> DomItem::lookup(const QString &symbolName, LookupType type, LookupOptions opts,
+ const ErrorHandler &errorHandler) const
+{
+ QList<DomItem> res;
+ visitLookup(
+ symbolName,
+ [&res](const DomItem &el) {
+ res.append(el);
+ return true;
+ },
+ type, opts, errorHandler);
+ return res;
+}
+
+DomItem DomItem::lookupFirst(const QString &symbolName, LookupType type, LookupOptions opts,
+ const ErrorHandler &errorHandler) const
+{
+ DomItem res;
+ visitLookup(
+ symbolName,
+ [&res](const DomItem &el) {
+ res = el;
+ return false;
+ },
+ type, opts, errorHandler);
+ return res;
+}
+
+quintptr DomItem::id() const
+{
+ return visitEl([](auto &&b) { return b->id(); });
+}
+
+Path DomItem::pathFromOwner() const
+{
+ return visitEl([this](auto &&e) { return e->pathFromOwner(*this); });
+}
+
+QString DomItem::canonicalFilePath() const
+{
+ return visitEl([this](auto &&e) { return e->canonicalFilePath(*this); });
+}
+
+DomItem DomItem::fileLocationsTree() const
+{
+ 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();
+}
+
+DomItem DomItem::fileLocations() const
+{
+ return fileLocationsTree().field(Fields::infoItem);
+}
+
+MutableDomItem DomItem::makeCopy(DomItem::CopyOption option) const
+{
+ 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()));
+}
+
+bool DomItem::commitToBase(const std::shared_ptr<DomEnvironment> &validEnvPtr) const
+{
+ DomItem env = environment();
+ if (std::shared_ptr<DomEnvironment> envPtr = env.ownerAs<DomEnvironment>()) {
+ return envPtr->commitToBase(env, validEnvPtr);
+ }
+ return false;
+}
+
+bool DomItem::visitLocalSymbolsNamed(const QString &name, function_ref<bool(const DomItem &)> visitor) const
+{
+ 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;
+}
+
+DomItem DomItem::operator[](const QString &cName) const
+{
+ if (internalKind() == DomType::Map)
+ return key(cName);
+ return field(cName);
+}
+
+DomItem DomItem::operator[](QStringView cName) const
+{
+ if (internalKind() == DomType::Map)
+ return key(cName.toString());
+ return field(cName);
+}
+
+DomItem DomItem::operator[](const Path &p) const
+{
+ return path(p);
+}
+
+QCborValue DomItem::value() const
+{
+ return base()->value();
+}
+
+void DomItem::dumpPtr(const Sink &sink) const
+{
+ 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"}");
+}
+
+void DomItem::dump(
+ const Sink &s, int indent,
+ function_ref<bool(const DomItem &, const PathEls::PathComponent &, const DomItem &)> filter) const
+{
+ visitEl([this, s, indent, filter](auto &&e) { e->dump(*this, s, indent, filter); });
+}
+
+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
+{
+ FileWriter localFw;
+ if (!fw)
+ fw = &localFw;
+ switch (fw->write(
+ path,
+ [this, indent, filter](QTextStream &ts) {
+ this->dump([&ts](QStringView s) { ts << s; }, indent, filter);
+ return true;
+ },
+ nBackups)) {
+ case FileWriter::Status::ShouldWrite:
+ case FileWriter::Status::SkippedDueToFailure:
+ qWarning() << "Failure dumping " << canonicalPath() << " to " << path;
+ break;
+ case FileWriter::Status::DidWrite:
+ case FileWriter::Status::SkippedEqual:
+ break;
+ }
+ return fw->status;
+}
+
+QString DomItem::toString() const
+{
+ return dumperToString([this](const Sink &s){ dump(s); });
+}
+
+int DomItem::derivedFrom() const
+{
+ 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);
+}
+
+int DomItem::revision() const
+{
+ 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);
+}
+
+QDateTime DomItem::createdAt() const
+{
+ 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);
+}
+
+QDateTime DomItem::frozenAt() const
+{
+ 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);
+}
+
+QDateTime DomItem::lastDataUpdateAt() const
+{
+ 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);
+}
+
+void DomItem::addError(ErrorMessage &&msg) const
+{
+ 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);
+}
+
+ErrorHandler DomItem::errorHandler() const
+{
+ // We need a copy here. Error handlers may be called when this is gone.
+ return [self = *this](const ErrorMessage &m) { self.addError(ErrorMessage(m)); };
+}
+
+void DomItem::clearErrors(const ErrorGroups &groups, bool iterate) const
+{
+ 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;
+ });
+ }
+}
+
+bool DomItem::iterateErrors(
+ function_ref<bool(const DomItem &, const ErrorMessage &)> visitor, bool iterate,
+ Path inPath) const
+{
+ 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;
+}
+
+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);
+}
+
+bool DomItem::iterateDirectSubpaths(DirectVisitor v) const
+{
+ return visitEl(
+ [this, v](auto &&el) { return el->iterateDirectSubpaths(*this, v); });
+}
+
+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::subReferenceItem(const PathEls::PathComponent &c, const Path &referencedObject) const
+{
+ 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)));
+ }
+}
+
+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);
+}
+
+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);
+}
+
+/*!
+ \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(const std::shared_ptr<DomEnvironment> &envPtr):
+ DomItem(envPtr, envPtr, Path(), envPtr.get())
+{
+}
+
+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()
+{}
+
+Path Empty::pathFromOwner(const DomItem &) const
+{
+ return Path();
+}
+
+Path Empty::canonicalPath(const DomItem &) const
+{
+ return Path();
+}
+
+bool Empty::iterateDirectSubpaths(const DomItem &, DirectVisitor) const
+{
+ return true;
+}
+
+DomItem Empty::containingObject(const DomItem &self) const
+{
+ return self;
+}
+
+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(const Path &pathFromOwner, const Map::LookupFunction &lookup,
+ const Keys &keys, const QString &targetType)
+ : DomElement(pathFromOwner), m_lookup(lookup), m_keys(keys), m_targetType(targetType)
+{}
+
+quintptr Map::id() const
+{
+ return quintptr(0);
+}
+
+bool Map::iterateDirectSubpaths(const DomItem &self, DirectVisitor visitor) const
+{
+ 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;
+}
+
+const QSet<QString> Map::keys(const DomItem &self) const
+{
+ return m_keys(self);
+}
+
+DomItem Map::key(const DomItem &self, const QString &name) const
+{
+ return m_lookup(self, name);
+}
+
+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();
+ switch (dK) {
+ case DomKind::Object:
+ sink(u"{ \"~type~\":");
+ sinkEscaped(sink, typeName());
+ comma = true;
+ break;
+ case DomKind::Value:
+ {
+ QJsonValue v = value().toJsonValue();
+ if (v.isString())
+ sinkEscaped(sink, v.toString());
+ else if (v.isBool())
+ if (v.toBool())
+ sink(u"true");
+ else
+ sink(u"false");
+ else if (v.isDouble())
+ if (value().isInteger())
+ sink(QString::number(value().toInteger()));
+ else
+ sink(QString::number(v.toDouble()));
+ else {
+ sink(QString::fromUtf8(QJsonDocument::fromVariant(v.toVariant()).toJson()));
+ }
+ break;
+ }
+ case DomKind::Empty:
+ sink(u"null");
+ break;
+ case DomKind::List:
+ sink(u"[");
+ break;
+ case DomKind::Map:
+ sink(u"{");
+ break;
+ case DomKind::ScriptElement:
+ // nothing to print
+ break;
+ }
+ auto closeParens = qScopeGuard(
+ [dK, sink, indent]{
+ switch (dK) {
+ case DomKind::Object:
+ sinkNewline(sink, indent);
+ sink(u"}");
+ break;
+ case DomKind::Value:
+ break;
+ case DomKind::Empty:
+ break;
+ case DomKind::List:
+ sinkNewline(sink, indent);
+ sink(u"]");
+ break;
+ case DomKind::Map:
+ sinkNewline(sink, indent);
+ sink(u"}");
+ break;
+ case DomKind::ScriptElement:
+ // nothing to print
+ break;
+ }
+ });
+ 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(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)
+{}
+
+quintptr List::id() const
+{
+ return quintptr(0);
+}
+
+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"[");
+ 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(const DomItem &self, DirectVisitor visitor) const
+{
+ if (m_iterator) {
+ 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) {
+ if (!visitor(PathEls::Index(i), [this, &self, i]() { return index(self, i); }))
+ return false;
+ }
+ return true;
+}
+
+index_type List::indexes(const DomItem &self) const
+{
+ return m_length(self);
+}
+
+DomItem List::index(const DomItem &self, index_type index) const
+{
+ return m_lookup(self, index);
+}
+
+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");
+ return m_pathFromOwner;
+}
+
+Path DomElement::canonicalPath(const DomItem &self) const
+{
+ Q_ASSERT(m_pathFromOwner && "uninitialized DomElement");
+ return self.owner().canonicalPath().path(m_pathFromOwner);
+}
+
+DomItem DomElement::containingObject(const DomItem &self) const
+{
+ Q_ASSERT(m_pathFromOwner && "uninitialized DomElement");
+ return DomBase::containingObject(self);
+}
+
+void DomElement::updatePathFromOwner(const Path &newPath)
+{
+ //if (!domTypeCanBeInline(kind()))
+ m_pathFromOwner = newPath;
+}
+
+bool Reference::shouldCache() const
+{
+ 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(const Path &referredObject, const Path &pathFromOwner, const SourceLocation &)
+ : DomElement(pathFromOwner), referredObjectPath(referredObject)
+{
+}
+
+quintptr Reference::id() const
+{
+ return quintptr(0);
+}
+
+bool Reference::iterateDirectSubpaths(const DomItem &self, DirectVisitor visitor) const
+{
+ 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.subDataItemField(Fields::referredObjectPath, referredObjectPath.toString());
+ if (Fields::get == name)
+ return get(self);
+ return DomItem();
+}
+
+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
+{
+ return DomItem();
+}
+
+DomItem Reference::key(const DomItem &, const QString &) const
+{
+ return DomItem();
+}
+
+DomItem Reference::get(const DomItem &self, const ErrorHandler &h, QList<Path> *visitedRefs) const
+{
+ 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;
+}
+
+/*!
+\internal
+\class QQmlJS::Dom::OwningItem
+
+\brief A DomItem that owns other DomItems and is managed through a shared pointer
+
+This is the unit of update of the Dom model, only OwningItem can be updated after
+exposed, to update a single element one has to copy its owner, change it, and expose an new one.
+The non owning contents of an exposed OwiningItem can safely be considered immutable, and pointers
+to them must remain valid for the whole lifetime of the OwningItem (that is managed with
+shared_ptr), only the sub OwningItems *might* be change.
+The OwningItem has a mutex that controls it access (mainly for the errors, observers, and sub
+OwningItems), Access to the rest is *not* controlled, it should either be by a single thread
+(during construction), or immutable (after pubblication).
+
+*/
+
+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, 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::currentDateTimeUtc()),
+ m_lastDataUpdateAt(o.lastDataUpdateAt()),
+ m_frozenAt(QDateTime::fromMSecsSinceEpoch(0, QTimeZone::UTC))
+{
+ QMultiMap<Path, ErrorMessage> my_errors;
+ {
+ QMutexLocker l1(o.mutex());
+ my_errors = o.m_errors;
+
+ }
+ {
+ QMutexLocker l2(mutex());
+ m_errors = my_errors;
+ }
+}
+
+
+int OwningItem::nextRevision()
+{
+ static QAtomicInt nextRev(0);
+ return ++nextRev;
+}
+
+bool OwningItem::iterateDirectSubpaths(const DomItem &self, DirectVisitor visitor) const
+{
+ bool cont = true;
+ 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;
+}
+
+DomItem OwningItem::containingObject(const DomItem &self) const
+{
+ Source s = self.canonicalPath().split();
+ if (s.pathFromSource) {
+ if (!s.pathToSource)
+ return DomItem();
+ return self.path(s.pathToSource);
+ }
+ return DomItem();
+}
+
+int OwningItem::derivedFrom() const
+{
+ return m_derivedFrom;
+}
+
+int OwningItem::revision() const
+{
+ return m_revision;
+}
+
+bool OwningItem::frozen() const
+{
+ return m_frozenAt > m_createdAt;
+}
+
+bool OwningItem::freeze()
+{
+ if (!frozen()) {
+ m_frozenAt = QDateTime::currentDateTimeUtc();
+ if (m_frozenAt <= m_createdAt)
+ m_frozenAt = m_createdAt.addSecs(1);
+ return true;
+ }
+ return false;
+}
+
+QDateTime OwningItem::createdAt() const
+{
+ return m_createdAt;
+}
+
+QDateTime OwningItem::lastDataUpdateAt() const
+{
+ return m_lastDataUpdateAt;
+}
+
+QDateTime OwningItem::frozenAt() const
+{
+ return m_frozenAt;
+}
+
+void OwningItem::refreshedDataAt(QDateTime tNew)
+{
+ if (m_lastDataUpdateAt < tNew) // remove check?
+ m_lastDataUpdateAt = tNew;
+}
+
+void OwningItem::addError(const DomItem &, ErrorMessage &&msg)
+{
+ addErrorLocal(std::move(msg));
+}
+
+void OwningItem::addErrorLocal(ErrorMessage &&msg)
+{
+ QMutexLocker l(mutex());
+ quint32 &c = m_errorsCounts[msg];
+ c += 1;
+ if (c == 1)
+ m_errors.insert(msg.path, msg);
+}
+
+void OwningItem::clearErrors(const ErrorGroups &groups)
+{
+ QMutexLocker l(mutex());
+ auto it = m_errors.begin();
+ while (it != m_errors.end()) {
+ if (it->errorGroups == groups)
+ it = m_errors.erase(it);
+ else
+ ++it;
+ }
+}
+
+bool OwningItem::iterateErrors(
+ const DomItem &self, function_ref<bool(const DomItem &, const ErrorMessage &)> visitor,
+ const Path &inPath)
+{
+ QMultiMap<Path, ErrorMessage> myErrors;
+ {
+ QMutexLocker l(mutex());
+ myErrors = m_errors;
+ }
+ auto it = myErrors.lowerBound(inPath);
+ auto end = myErrors.end();
+ while (it != end && it.key().mid(0, inPath.length()) == inPath) {
+ if (!visitor(self, *it++))
+ return false;
+ }
+ return true;
+}
+
+bool OwningItem::iterateSubOwners(const DomItem &self, function_ref<bool(const DomItem &owner)> visitor)
+{
+ 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;
+ });
+}
+
+ErrorHandler MutableDomItem::errorHandler()
+{
+ MutableDomItem self;
+ return [&self](const ErrorMessage &m) { self.addError(ErrorMessage(m)); };
+}
+
+MutableDomItem MutableDomItem::addPrototypePath(const Path &prototypePath)
+{
+ if (QmlObject *el = mutableAs<QmlObject>()) {
+ return path(el->addPrototypePath(prototypePath));
+ } else {
+ Q_ASSERT(false && "setPrototypePath on non qml scope");
+ return {};
+ }
+}
+
+MutableDomItem MutableDomItem::setNextScopePath(const Path &nextScopePath)
+{
+ if (QmlObject *el = mutableAs<QmlObject>()) {
+ el->setNextScopePath(nextScopePath);
+ return field(Fields::nextScope);
+ } else {
+ Q_ASSERT(false && "setNextScopePath on non qml scope");
+ return {};
+ }
+}
+
+MutableDomItem MutableDomItem::setPropertyDefs(QMultiMap<QString, PropertyDefinition> propertyDefs)
+{
+ if (QmlObject *el = mutableAs<QmlObject>()) {
+ el->setPropertyDefs(propertyDefs);
+ return field(Fields::propertyDefs);
+ } else {
+ Q_ASSERT(false && "setPropertyDefs on non qml scope");
+ return {};
+ }
+}
+
+MutableDomItem MutableDomItem::setBindings(QMultiMap<QString, Binding> bindings)
+{
+ if (QmlObject *el = mutableAs<QmlObject>()) {
+ el->setBindings(bindings);
+ return field(Fields::bindings);
+ } else {
+ Q_ASSERT(false && "setBindings on non qml scope");
+ return {};
+ }
+}
+
+MutableDomItem MutableDomItem::setMethods(QMultiMap<QString, MethodInfo> functionDefs)
+{
+ 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);
+}
+MutableDomItem MutableDomItem::setScript(const std::shared_ptr<ScriptExpression> &exp)
+{
+ 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();
+}
+
+MutableDomItem MutableDomItem::setCode(const QString &code)
+{
+ 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 MutableDomItem();
+}
+
+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();
+}
+
+MutableDomItem MutableDomItem::addMethod(const MethodInfo &functionDef, AddOption option)
+{
+ if (QmlObject *el = mutableAs<QmlObject>())
+ return el->addMethod(*this, functionDef, option);
+ else
+ Q_ASSERT(false && "addMethod on non qml scope");
+ return MutableDomItem();
+}
+
+MutableDomItem MutableDomItem::addChild(QmlObject child)
+{
+ 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](const Sink &s) { c.dump(s); }, debug);
+ return debug;
+}
+
+QDebug operator<<(QDebug debug, const MutableDomItem &c)
+{
+ 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"