Revision control
++ // 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 "qqmldomfilelocations_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 <qqmldomlinewriterfactory_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/QRegularExpression>
++ #include <QtCore/QScopeGuard>
++ #include <QtCore/QtGlobal>
++ #include <QtCore/QTimeZone>
++ #include <optional>
++ #include <type_traits>
++ #include <utility>
++
++ QT_BEGIN_NAMESPACE
++
++ Q_LOGGING_CATEGORY(writeOutLog, "qt.qmldom.writeOut", QtWarningMsg);
++ Q_STATIC_LOGGING_CATEGORY(refLog, "qt.qmldom.ref", QtWarningMsg);
++
++ namespace QQmlJS {
++ namespace Dom {
++
++ 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 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::JsFile:
++ case DomType::QmlFile:
++ 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::GlobalComponent: // global scope (enums -> qmlObj)
++ case DomType::JsResource: // js resurce (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.invokeVisitorOnValue(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.invokeVisitorOnValue(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,
++ // we can't lazy load files from an empty path, but non-existent paths where canonicalPath
++ // is empty are OK
++ canonicalPath.isEmpty() ? path : 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::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();
++ }
++
++ /*!
++ \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 ¤tPath : 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, 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::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<QString, LookupType> stringToLookupTypeMap()
++ {
++ static auto map = []() {
++ QMetaEnum metaEnum = QMetaEnum::fromType<LookupType>();
++ QMap<QString, LookupType> res;
++ for (int i = 0; i < metaEnum.keyCount(); ++i) {
++ res[QString::fromUtf8(metaEnum.key(i)).toLower()] = LookupType(metaEnum.value(i));
++ }
++ return res;
++ }();
++ return map;
++ }
++
++ static DomItem rootFromContext(const DomItem &root, PathRoot contextId)
++ {
++ switch (contextId) {
++ case PathRoot::Modules:
++ return root.environment().field(Fields::moduleIndexWithUri);
++ case PathRoot::Libs:
++ return root.environment()[Fields::plugins];
++ case PathRoot::Top:
++ return root.top();
++ case PathRoot::Env:
++ return root.environment();
++ case PathRoot::Universe:
++ return root.environment()[u"universe"];
++ default:
++ return DomItem{};
++ }
++ }
++
++ /*!
++ \internal
++ Is used to resolve the "get" field of QQmlJS::Dom::Reference. Reference contains a link to a type,
++ and its "get" field does return the DomItem for the linked type. For example, a type has a link to
++ its base type.
++ */
++ static DomItem resolveReference(const DomItem &it, const Path &refRef, QList<Path> *visitedRefs,
++ const ErrorHandler &errorHandler, const ErrorGroups &errorGroup)
++ {
++ if (visitedRefs->contains(refRef)) {
++ errorGroup
++ .error([visitedRefs, refRef](const Sink &sink) {
++ const QString msg = DomItem::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);
++ return {};
++ }
++
++ const Reference *ref = it.as<Reference>();
++ Q_ASSERT(ref);
++ visitedRefs->append(refRef);
++ DomItem resolveRes;
++ it.resolve(
++ ref->referredObjectPath,
++ [&resolveRes](Path, const DomItem &r) {
++ resolveRes = r;
++ return false;
++ },
++ errorHandler, ResolveOption::None, ref->referredObjectPath, visitedRefs);
++ return resolveRes;
++ }
++
++ static LookupOptions resolveLookupOptions(const PathCurrent ¤t, const DomItem &it)
++ {
++ LookupOptions opt = LookupOption::Normal;
++ if (current == PathCurrent::LookupStrict)
++ return opt | LookupOption::Strict;
++
++ 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;
++ }
++ return opt;
++ }
++
++ static std::optional<LookupType> lookupTypeFromString(const QString &expectedType)
++ {
++ const auto &map = stringToLookupTypeMap();
++ auto it = map.find(expectedType.toLower());
++ if (it == map.end())
++ return {};
++ return *it;
++ }
++
++ 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;
++ QList<ResolveToDo> toDos(1); // invariant: always increase pathIndex to guarantee end even with only partial visited match
++ if (path.headKind() == Path::Kind::Root) {
++ DomItem root = rootFromContext(*this, path.headRoot());
++ if (!root) {
++ 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 = std::make_pair(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) {
++ it = resolveReference(it, it.canonicalPath(),
++ visitedRefs == nullptr ? &vRefs : visitedRefs,
++ errorHandler, myResolveErrors());
++ } 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:
++ myResolveErrors()
++ .error(tr("Empty is not supported inside paths"))
++ .handle(errorHandler);
++ return false;
++ 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::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: {
++ const LookupOptions opt = resolveLookupOptions(current, it);
++ 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 = std::make_pair(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();
++ auto lookupType = lookupTypeFromString(expectedType);
++ if (!lookupType) {
++ myResolveErrors()
++ .error(tr("Type for lookup was expected to be one of '%1', not "
++ "%2")
++ .arg(stringToLookupTypeMap().keys().join(", "_L1),
++ 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;
++ },
++ *(std::move(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 iterateDirectSubpaths 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 iterateDirectSubpaths 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;
++ iterateDirectSubpaths([&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();
++ }
++
++ DomItem::WriteOutCheckResult DomItem::performWriteOutChecks(const DomItem &reformatted,
++ OutWriter &ow,
++ WriteOutChecks extraChecks) const
++ {
++ auto compare = [this](const DomItem &obj1, const DomItem &obj2, QStringView obj2Name,
++ const FieldFilter &filter) {
++ const auto diffList = domCompareStrList(obj1, obj2, filter, DomCompareStrList::AllDiffs);
++ if (!diffList.isEmpty()) {
++ qCWarning(writeOutLog).noquote().nospace()
++ << obj2Name << " writeOut of " << this->canonicalFilePath() << " has changes:\n"
++ << diffList.join(QString());
++ return false;
++ }
++ return true;
++ };
++ auto checkStability = [&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) {
++ qCWarning(writeOutLog).noquote().nospace()
++ << objName << " non stable writeOut of " << this->canonicalFilePath() << ":"
++ << lineDiff(expected, ow2.writtenStr, 2);
++ return false;
++ }
++ return true;
++ };
++
++ 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);
++ 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, 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;
++ auto currentFileItem = fileObject();
++ writeOut(ow);
++ ow.eof();
++ WriteOutCheckResult result = WriteOutCheckResult::Success;
++ if (extraChecks)
++ result = performWriteOutChecks(currentFileItem, ow, extraChecks);
++ return result == WriteOutCheckResult::Success ? bool(currentFileItem) : false;
++ }
++
++ bool DomItem::writeOut(const QString &path, 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) {
++ auto lw = createLineWriter([&ts](QStringView s) { ts << s; }, path, options);
++ OutWriter ow(getFileItemOwner(fileObject()), *lw);
++ return writeOutForFile(ow, extraChecks);
++ });
++ 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 iterateDirectSubpaths(
++ [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.withComponent(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 ¤t,
++ 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, ¤t, 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::JsResource:
++ case DomType::GlobalComponent:
++ first = false;
++ if (DomItem next = current.field(Fields::objects).index(0))
++ toDo.append(next);
++ 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)
++ {
++ QList<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 = std::make_pair(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:
++ return true;
++ }
++ 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([](auto &&e) { return e->pathFromOwner(); });
++ }
++
++ QString DomItem::canonicalFilePath() const
++ {
++ return visitEl([this](auto &&e) { return e->canonicalFilePath(*this); });
++ }
++
++ 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->domCreationOption());
++ 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::Default, 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::JsFile:
++ newEnvPtr->addJsFile(newItem.ownerAs<JsFile>(), AddOption::Overwrite);
++ break;
++ case DomType::QmlFile:
++ newEnvPtr->addQmlFile(newItem.ownerAs<QmlFile>(), AddOption::Overwrite);
++ break;
++ case DomType::MockOwner:
++ case DomType::ScriptExpression:
++ case DomType::AstComments:
++ case DomType::LoadInfo:
++ case DomType::FileLocationsNode:
++ 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::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;
++ }
++ 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 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;
++ })) {
++ 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().withComponent(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().withComponent(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({}, 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));
++ return tFile.fileObject();
++ }
++
++ Empty::Empty()
++ {}
++
++ Path Empty::pathFromOwner() 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:
++ {
++ sink(QString::fromUtf8(value().toJsonValue().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
++ {
++ const auto fLoc = FileLocations::treeOf(self);
++ ow.writeRegion(fLoc, LeftBracketRegion);
++ bool first = true;
++ iterateDirectSubpaths(self,
++ [&ow, &first, &fLoc, compact](const PathEls::PathComponent &,
++ function_ref<DomItem()> elF) {
++ if (first)
++ first = false;
++ else
++ ow.writeRegion(fLoc, CommaTokenRegion).ensureSpace();
++ if (!compact)
++ ow.ensureNewline(1);
++ DomItem el = elF();
++ el.writeOut(ow);
++ return true;
++ });
++ if (!compact && !first)
++ ow.newline();
++ ow.writeRegion(fLoc, RightBracketRegion);
++ }
++
++ DomElement::DomElement(const Path &pathFromOwner) : m_pathFromOwner(pathFromOwner) { }
++
++ Path DomElement::canonicalPath(const DomItem &self) const
++ {
++ Q_ASSERT(m_pathFromOwner && "uninitialized DomElement");
++ return self.owner().canonicalPath().withPath(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:
++ 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.invokeVisitorOnLazyField(visitor, Fields::referredObjectPath, [this]() {
++ return referredObjectPath.toString();
++ });
++ cont = cont && visitor(PathEls::Field(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.subDataItem(PathEls::Field(Fields::referredObjectPath),
++ referredObjectPath.toString());
++ }
++ if (Fields::get == name)
++ return get(self);
++ return DomItem();
++ }
++
++ QList<QString> Reference::fields(const DomItem &) const
++ {
++ return QList<QString>({Fields::referredObjectPath.toString(), Fields::get.toString()});
++ }
++
++ 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 && visitor(PathEls::Field(Fields::errors), [&self, this]() {
++ QMultiMap<Path, ErrorMessage> myErrors = localErrors();
++ return self.subMapItem(Map(
++ self.pathFromOwner().withField(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();
++ Path p2 = el2->pathFromOwner();
++ if (p1 != p2)
++ return false;
++ return true;
++ });
++ }
++
++ ErrorHandler MutableDomItem::errorHandler()
++ {
++ MutableDomItem self;
++ return [&self](const ErrorMessage &m) { self.addError(ErrorMessage(m)); };
++ }
++
++ 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();
++ }
++
++
++ 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
++ {
++ const auto fLoc = FileLocations::treeOf(self);
++ ow.writeRegion(fLoc, LeftBracketRegion);
++ 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.writeRegion(fLoc, CommaTokenRegion).ensureSpace();
++ if (!compact)
++ ow.ensureNewline(1);
++ DomItem el = index(self, i);
++ el.writeOut(ow);
++ }
++ if (!compact && !first)
++ ow.newline();
++ ow.writeRegion(fLoc, 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);
++ }
++
++ std::shared_ptr<ExternalOwningItem> getFileItemOwner(const DomItem &fileItem)
++ {
++ switch (fileItem.internalKind()) {
++ case DomType::JsFile:
++ return fileItem.ownerAs<JsFile>();
++ case DomType::QmlFile:
++ return fileItem.ownerAs<QmlFile>();
++ default:
++ Q_UNREACHABLE_RETURN({});
++ }
++ }
++
++ } // end namespace Dom
++ } // end namespace QQmlJS
++
++ QT_END_NAMESPACE
++
++ #include "moc_qqmldomitem_p.cpp"