diff options
Diffstat (limited to 'src/qmldom/qqmldomelements.cpp')
-rw-r--r-- | src/qmldom/qqmldomelements.cpp | 1263 |
1 files changed, 1263 insertions, 0 deletions
diff --git a/src/qmldom/qqmldomelements.cpp b/src/qmldom/qqmldomelements.cpp new file mode 100644 index 0000000000..2600035512 --- /dev/null +++ b/src/qmldom/qqmldomelements.cpp @@ -0,0 +1,1263 @@ +/**************************************************************************** +** +** Copyright (C) 2021 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtQml module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +**/ +#include "qqmldomelements_p.h" +#include "qqmldomcomments_p.h" +#include "qqmldomastdumper_p.h" +#include "qqmldommock_p.h" +#include "qqmldomtop_p.h" +#include "qqmldomexternalitems_p.h" + +#include <QtQml/private/qqmljslexer_p.h> +#include <QtQml/private/qqmljsparser_p.h> +#include <QtQml/private/qqmljsengine_p.h> +#include <QtQml/private/qqmljsastvisitor_p.h> +#include <QtQml/private/qqmljsast_p.h> + +#include <QtCore/QScopeGuard> +#include <QtCore/QRegularExpression> +#include <QtCore/QDir> +#include <QtCore/QBasicMutex> + +#include <optional> + +QT_BEGIN_NAMESPACE + +namespace QQmlJS { +namespace Dom { + +namespace Paths { + +Path moduleIndexPath(QString uri, int majorVersion, ErrorHandler errorHandler) +{ + QString version = QString::number(majorVersion); + if (majorVersion == Version::Latest) + version = QLatin1String("Latest"); + else if (majorVersion == Version::Undefined) + version = QString(); + if (uri.startsWith(u"file://") || uri.startsWith(u"http://") || uri.startsWith(u"https://")) { + if (majorVersion != Version::Undefined) + Path::myErrors() + .error(Path::tr("The module directory import %1 cannot have a version") + .arg(uri)) + .handle(errorHandler); + version = QString(); + } else { + QRegularExpression moduleRe(QLatin1String(R"(\A\w+(?:\.\w+)*\Z)")); + auto m = moduleRe.match(uri); + if (!m.isValid()) + Path::myErrors() + .error(Path::tr("Invalid module name in import %1").arg(uri)) + .handle(errorHandler); + } + return Path::Root(PathRoot::Env).field(Fields::moduleIndexWithUri).key(uri).key(version); +} + +Path moduleScopePath(QString uri, Version version, ErrorHandler errorHandler) +{ + if (uri.startsWith(u"file://") || uri.startsWith(u"http://") || uri.startsWith(u"https://")) { + if (version.isValid()) + Path::myErrors() + .error(Path::tr("The module directory import %1 cannot have a version") + .arg(uri)) + .handle(errorHandler); + version = {}; + } else { + QRegularExpression moduleRe(QLatin1String(R"(\A\w+(?:\.\w+)*\Z)")); + auto m = moduleRe.match(uri); + if (!m.isValid()) + Path::myErrors() + .error(Path::tr("Invalid module name in import %1").arg(uri)) + .handle(errorHandler); + } + return Path::Root(PathRoot::Env) + .field(Fields::moduleIndexWithUri) + .key(uri) + .key(version.majorSymbolicString()) + .field(Fields::moduleScope) + .key(version.minorString()); +} + +Path moduleScopePath(QString uri, QString version, ErrorHandler errorHandler) +{ + Version v = Version::fromString(version); + if (!version.isEmpty() && !(v.isValid() || v.isLatest())) + Path::myErrors().error(Path::tr("Invalid Version %1").arg(version)).handle(errorHandler); + return moduleScopePath(uri, v, errorHandler); +} + +} // end namespace Paths + +static ErrorGroups domParsingErrors() +{ + static ErrorGroups res = { { DomItem::domErrorGroup, NewErrorGroup("Parsing") } }; + return res; +} + +bool CommentableDomElement::iterateDirectSubpaths(DomItem &self, DirectVisitor visitor) +{ + bool cont = true; + cont = cont && self.dvWrapField(visitor, Fields::comments, m_comments); + return cont; +} + +void Component::updatePathFromOwner(Path newPath) +{ + DomElement::updatePathFromOwner(newPath); + updatePathFromOwnerMultiMap(m_enumerations, newPath.field(Fields::enumerations)); + updatePathFromOwnerQList(m_objects, newPath.field(Fields::objects)); +} + +Component::Component(QString name) : CommentableDomElement(Path()), m_name(name) { } + +Component::Component(Path pathFromOwner) : CommentableDomElement(pathFromOwner) { } + +bool Component::iterateDirectSubpaths(DomItem &self, DirectVisitor visitor) +{ + bool cont = CommentableDomElement::iterateDirectSubpaths(self, visitor); + cont = cont && self.dvValueField(visitor, Fields::name, name()); + cont = cont && self.dvWrapField(visitor, Fields::enumerations, m_enumerations); + cont = cont && self.dvWrapField(visitor, Fields::objects, m_objects); + cont = cont && self.dvValueField(visitor, Fields::isSingleton, isSingleton()); + cont = cont && self.dvValueField(visitor, Fields::isCreatable, isCreatable()); + cont = cont && self.dvValueField(visitor, Fields::isComposite, isComposite()); + cont = cont && self.dvValueField(visitor, Fields::attachedTypeName, attachedTypeName()); + cont = cont && self.dvReferenceField(visitor, Fields::attachedType, attachedTypePath(self)); + return cont; +} + +DomItem Component::field(DomItem &self, QStringView name) +{ + switch (name.length()) { + case 4: + if (name == Fields::name) + return self.wrapField(Fields::name, m_name); + break; + case 7: + if (name == Fields::objects) + return self.wrapField(Fields::objects, m_objects); + break; + default: + break; + } + return DomBase::field(self, name); +} + +Path Component::addObject(const QmlObject &object, QmlObject **oPtr) +{ + return appendUpdatableElementInQList(pathFromOwner().field(Fields::objects), m_objects, object, + oPtr); +} + +bool QmlComponent::iterateDirectSubpaths(DomItem &self, DirectVisitor visitor) +{ + bool cont = Component::iterateDirectSubpaths(self, visitor); + cont = cont && self.dvWrapField(visitor, Fields::ids, m_ids); + cont = cont && self.dvValueLazyField(visitor, Fields::subComponents, [this, &self]() { + return this->subComponents(self); + }); + return cont; +} + +void QmlComponent::updatePathFromOwner(Path newPath) +{ + Component::updatePathFromOwner(newPath); + updatePathFromOwnerMultiMap(m_ids, newPath.field(Fields::annotations)); +} + +QList<QString> QmlComponent::subComponentsNames(DomItem &self) const +{ + DomItem components = self.owner().field(Fields::components); + QSet<QString> cNames = components.keys(); + QString myNameDot = self.pathFromOwner()[1].headName(); + if (!myNameDot.isEmpty()) + myNameDot += QLatin1Char('.'); + QList<QString> subNames; + for (QString cName : cNames) + if (cName.startsWith(myNameDot) + && !QStringView(cName).mid(myNameDot.length()).contains(QLatin1Char('.')) + && !cName.isEmpty()) + subNames.append(cName); + std::sort(subNames.begin(), subNames.end()); + return subNames; +} + +QList<DomItem> QmlComponent::subComponents(DomItem &self) const +{ + DomItem components = self.owner().field(Fields::components); + QList<DomItem> res; + for (QString cName : subComponentsNames(self)) + for (DomItem comp : components.key(cName).values()) + res.append(comp); + return res; +} + +Version Version::fromString(QStringView v) +{ + if (v.isEmpty()) + return Version(Latest, Latest); + QRegularExpression r( + QRegularExpression::anchoredPattern(QStringLiteral(uR"(([0-9]*)(?:\.([0-9]*))?)"))); + auto m = r.match(v.toString()); + if (m.hasMatch()) { + bool ok; + int majorV = m.captured(1).toInt(&ok); + if (!ok) + majorV = Version::Undefined; + int minorV = m.captured(2).toInt(&ok); + if (!ok) + minorV = Version::Undefined; + return Version(majorV, minorV); + } + return {}; +} + +Version::Version(qint32 majorV, qint32 minorV) : majorVersion(majorV), minorVersion(minorV) { } + +bool Version::isLatest() const +{ + return majorVersion == Latest && minorVersion == Latest; +} + +bool Version::isValid() const +{ + return majorVersion >= 0 && minorVersion >= 0; +} + +QString Version::stringValue() const +{ + if (isLatest()) + return QString(); + if (minorVersion < 0) { + if (majorVersion < 0) + return QLatin1String("."); + else + return QString::number(majorVersion); + } + if (majorVersion < 0) + return QLatin1String(".") + QString::number(minorVersion); + return QString::number(majorVersion) + QChar::fromLatin1('.') + QString::number(minorVersion); +} + +bool Version::iterateDirectSubpaths(DomItem &self, DirectVisitor visitor) +{ + bool cont = true; + cont = cont && self.dvWrapField(visitor, Fields::majorVersion, majorVersion); + cont = cont && self.dvWrapField(visitor, Fields::minorVersion, minorVersion); + cont = cont && self.dvValueField(visitor, Fields::isLatest, isLatest()); + cont = cont && self.dvValueField(visitor, Fields::isValid, isValid()); + cont = cont && self.dvValueLazyField(visitor, Fields::stringValue, [this]() { + return this->stringValue(); + }); + return cont; +} + +QRegularExpression Import::importRe() +{ + static QRegularExpression res(QRegularExpression::anchoredPattern(QStringLiteral( + uR"((?<uri>\w+(?:\.\w+)*)(?:\W+(?<version>[0-9]+(?:\.[0-9]*)?))?(?:\W+as\W+(?<id>\w+))?$)"))); + return res; +} + +Import Import::fromUriString(QString importStr, Version v, QString importId, ErrorHandler handler) +{ + if (importStr.startsWith(u"http://") || importStr.startsWith(u"https://") + || importStr.startsWith(u"file://")) { + return Import(importStr, v, importId); + } else { + auto m = importRe().match(importStr); + if (m.hasMatch()) { + if (v.majorVersion == Version::Undefined && v.minorVersion == Version::Undefined) + v = Version::fromString(m.captured(2)); + else if (!m.captured(u"version").isEmpty()) + domParsingErrors() + .warning(tr("Version %1 in import string '%2' overridden by explicit " + "version %3") + .arg(m.captured(2), importStr, v.stringValue())) + .handle(handler); + if (importId.isEmpty()) + importId = m.captured(u"importId"); + else if (!m.captured(u"importId").isEmpty()) + domParsingErrors() + .warning(tr("namespace %1 in import string '%2' overridden by explicit " + "importId %3") + .arg(m.captured(u"importId"), importStr, importId)) + .handle(handler); + return Import(m.captured(u"uri").trimmed(), v, importId); + } + domParsingErrors() + .error(tr("Unexpected uri format in import '%1'").arg(importStr)) + .handle(handler); + return Import(); + } +} + +Import Import::fromFileString(QString importStr, QString baseDir, QString importId, + ErrorHandler handler) +{ + Version v; + if (importStr.startsWith(u"http://") || importStr.startsWith(u"https://") + || importStr.startsWith(u"file://")) + return Import(importStr, v, importId); + QFileInfo p(importStr); + if (p.isRelative()) + p = QFileInfo(QDir(baseDir).filePath(importStr)); + QString path = p.canonicalFilePath(); + if (path.isEmpty()) { + domParsingErrors() + .warning(tr("Non existing directory or file referred in uri of import '%1'") + .arg(importStr)) + .handle(handler); + path = p.filePath(); + } + return Import(QLatin1String("file://") + path, v, importId); +} + +bool Import::iterateDirectSubpaths(DomItem &self, DirectVisitor visitor) +{ + bool cont = true; + cont = cont && self.dvValueField(visitor, Fields::uri, uri); + cont = cont && self.dvWrapField(visitor, Fields::version, version); + if (!importId.isEmpty()) + cont = cont && self.dvValueField(visitor, Fields::importId, importId); + if (implicit) + cont = cont && self.dvValueField(visitor, Fields::implicit, implicit); + cont = cont && self.dvWrapField(visitor, Fields::comments, comments); + return cont; +} + +Id::Id(QString idName, Path referredObject) : name(idName), referredObjectPath(referredObject) { } + +bool Id::iterateDirectSubpaths(DomItem &self, DirectVisitor visitor) +{ + bool cont = true; + cont = cont && self.dvValueField(visitor, Fields::name, name); + cont = cont && self.dvReferenceField(visitor, Fields::referredObject, referredObjectPath); + cont = cont && self.dvWrapField(visitor, Fields::comments, comments); + cont = cont && self.dvWrapField(visitor, Fields::annotations, annotations); + return cont; +} + +void Id::updatePathFromOwner(Path newPath) +{ + updatePathFromOwnerQList(annotations, newPath.field(Fields::annotations)); +} + +Path Id::addAnnotation(Path selfPathFromOwner, const QmlObject &annotation, QmlObject **aPtr) +{ + return appendUpdatableElementInQList(selfPathFromOwner.field(Fields::annotations), annotations, + annotation, aPtr); +} + +QmlObject::QmlObject(Path pathFromOwner) : CommentableDomElement(pathFromOwner) { } + +bool QmlObject::iterateBaseDirectSubpaths(DomItem &self, DirectVisitor visitor) +{ + bool cont = CommentableDomElement::iterateDirectSubpaths(self, visitor); + if (!idStr().isEmpty()) + cont = cont && self.dvValueField(visitor, Fields::idStr, idStr()); + cont = cont && self.dvValueField(visitor, Fields::name, name()); + if (!prototypePaths().isEmpty()) + cont = cont && self.dvReferencesField(visitor, Fields::prototypes, m_prototypePaths); + if (nextScopePath()) + cont = cont && self.dvReferenceField(visitor, Fields::nextScope, nextScopePath()); + cont = cont && self.dvWrapField(visitor, Fields::propertyDefs, m_propertyDefs); + cont = cont && self.dvWrapField(visitor, Fields::bindings, m_bindings); + cont = cont && self.dvWrapField(visitor, Fields::methods, m_methods); + cont = cont && self.dvWrapField(visitor, Fields::children, m_children); + cont = cont && self.dvWrapField(visitor, Fields::annotations, m_annotations); + cont = cont && self.dvItemField(visitor, Fields::propertyInfos, [this, &self]() { + return self.subMapItem(Map( + pathFromOwner().field(Fields::propertyInfos), + [&self](DomItem &map, QString k) { + auto pInfo = self.propertyInfoWithName(k); + return map.wrap(PathEls::Key(k), pInfo); + }, + [&self](DomItem &) { return self.propertyInfoNames(); }, + QLatin1String("PropertyInfo"))); + }); + return cont; +} + +QList<QString> QmlObject::fields() const +{ + static QList<QString> myFields( + { QString::fromUtf16(Fields::comments), QString::fromUtf16(Fields::idStr), + QString::fromUtf16(Fields::name), QString::fromUtf16(Fields::prototypes), + QString::fromUtf16(Fields::nextScope), QString::fromUtf16(Fields::propertyDefs), + QString::fromUtf16(Fields::bindings), QString::fromUtf16(Fields::methods), + QString::fromUtf16(Fields::children), QString::fromUtf16(Fields::annotations), + QString::fromUtf16(Fields::propertyInfos) }); + return myFields; +} + +bool QmlObject::iterateDirectSubpaths(DomItem &self, DirectVisitor visitor) +{ + bool cont = iterateBaseDirectSubpaths(self, visitor); + cont = cont && self.dvValueLazyField(visitor, Fields::defaultPropertyName, [this, &self]() { + return defaultPropertyName(self); + }); + return cont; +} + +DomItem QmlObject::field(DomItem &self, QStringView name) +{ + switch (name.size()) { + case 4: + if (name == Fields::name) + return self.subDataItem(PathEls::Field(Fields::name), this->name()); + break; + case 5: + if (name == Fields::idStr) { + if (idStr().isEmpty()) + return DomItem(); + return self.subDataItem(PathEls::Field(Fields::idStr), idStr()); + } + break; + case 7: + if (name == Fields::methods) + return self.wrapField(Fields::methods, m_methods); + break; + case 8: + switch (name.at(1).unicode()) { + case u'i': + if (name == Fields::bindings) + return self.wrapField(Fields::bindings, m_bindings); + break; + case u'o': + if (name == Fields::comments) + return CommentableDomElement::field(self, name); + break; + case u'h': + if (name == Fields::children) + return self.wrapField(Fields::children, m_children); + break; + default: + break; + } + break; + case 9: + if (name == Fields::nextScope) { + if (nextScopePath()) + return self.subReferenceItem(PathEls::Field(Fields::nextScope), nextScopePath()); + else + return DomItem(); + } + break; + case 10: + if (name == Fields::prototypes) { + if (prototypePaths().isEmpty()) + return DomItem(); + return self.subReferencesItem(PathEls::Field(Fields::prototypes), m_prototypePaths); + } + break; + case 11: + if (name == Fields::annotations) + return self.wrapField(Fields::annotations, m_annotations); + break; + case 12: + return self.wrapField(Fields::propertyDefs, m_propertyDefs); + break; + case 13: + if (name == Fields::propertyInfos) + return self.subMapItem(Map( + pathFromOwner().field(Fields::propertyInfos), + [self](DomItem &map, QString k) mutable { + auto pInfo = self.propertyInfoWithName(k); + return map.wrap(PathEls::Key(k), pInfo); + }, + [self](DomItem &) mutable { return self.propertyInfoNames(); }, + QLatin1String("PropertyInfo"))); + break; + case 19: + if (name == Fields::defaultPropertyName) + return self.subDataItem(PathEls::Field(Fields::defaultPropertyName), + defaultPropertyName(self)); + break; + default: + break; + } + static QStringList knownLookups({ QString::fromUtf16(Fields::fileLocationsTree) }); + if (!knownLookups.contains(name)) + qCWarning(domLog()) << "Asked non existing field " << name << " in QmlObject " + << pathFromOwner(); + return DomItem(); +} + +void QmlObject::updatePathFromOwner(Path newPath) +{ + DomElement::updatePathFromOwner(newPath); + updatePathFromOwnerMultiMap(m_propertyDefs, newPath.field(Fields::propertyDefs)); + updatePathFromOwnerMultiMap(m_bindings, newPath.field(Fields::bindings)); + updatePathFromOwnerMultiMap(m_methods, newPath.field(Fields::methods)); + updatePathFromOwnerQList(m_children, newPath.field(Fields::children)); + updatePathFromOwnerQList(m_annotations, newPath.field(Fields::annotations)); +} + +QString QmlObject::localDefaultPropertyName() const +{ + if (!m_defaultPropertyName.isEmpty()) + return m_defaultPropertyName; + for (const PropertyDefinition &pDef : m_propertyDefs) + if (pDef.isDefaultMember) + return pDef.name; + return QString(); +} + +QString QmlObject::defaultPropertyName(DomItem &self) const +{ + QString dProp = localDefaultPropertyName(); + if (!dProp.isEmpty()) + return dProp; + QString res = QStringLiteral(u"data"); + self.visitPrototypeChain( + [&res](DomItem &obj) { + if (const QmlObject *objPtr = obj.as<QmlObject>()) { + QString dProp = objPtr->localDefaultPropertyName(); + if (!dProp.isEmpty()) { + res = dProp; + return false; + } + } + return true; + }, + VisitPrototypesOption::SkipFirst); + return res; +} + +bool QmlObject::iterateSubOwners(DomItem &self, function_ref<bool(DomItem &)> visitor) const +{ + bool cont = self.field(Fields::bindings).visitKeys([visitor](QString, DomItem &bs) { + return bs.visitIndexes([visitor](DomItem &b) { + DomItem v = b.field(Fields::value); + if (std::shared_ptr<ScriptExpression> vPtr = v.ownerAs<ScriptExpression>()) { + if (!visitor(v)) + return false; + return v.iterateSubOwners(visitor); // currently not needed, avoid? + } + return true; + }); + }); + cont = cont && self.field(Fields::children).visitIndexes([visitor](DomItem &qmlObj) { + if (const QmlObject *qmlObjPtr = qmlObj.as<QmlObject>()) { + return qmlObjPtr->iterateSubOwners(qmlObj, visitor); + } + Q_ASSERT(false); + return true; + }); + return cont; +} + +MutableDomItem QmlObject::addPropertyDef(MutableDomItem &self, PropertyDefinition propertyDef, + AddOption option) +{ + Path p = addPropertyDef(propertyDef, option); + if (p.last().headIndex(0) > 1) + self.owningItemPtr()->addErrorLocal(domParsingErrors().error( + tr("Repeated PropertyDefinition with name %1").arg(propertyDef.name))); + return self.owner().path(p); +} + +MutableDomItem QmlObject::addBinding(MutableDomItem &self, Binding binding, AddOption option) +{ + Path p = addBinding(binding, option); + if (p && p.last().headIndex(0) > 1) + self.owningItemPtr()->addErrorLocal( + domParsingErrors().error(tr("Repeated binding with name %1").arg(binding.name()))); + return self.owner().path(p); +} + +MutableDomItem QmlObject::addMethod(MutableDomItem &self, MethodInfo functionDef, AddOption option) +{ + Path p = addMethod(functionDef, option); + if (p.last().headIndex(0) > 1) + self.owningItemPtr()->addErrorLocal( + domParsingErrors().error(tr("Repeated Method with name %1").arg(functionDef.name))); + return self.owner().path(p); +} + +Binding::Binding(QString name, std::unique_ptr<BindingValue> value, BindingType bindingType) + : m_bindingType(bindingType), m_name(name), m_value(std::move(value)) +{ +} + +Binding::Binding(QString name, std::shared_ptr<ScriptExpression> value, BindingType bindingType) + : Binding(name, std::make_unique<BindingValue>(value), bindingType) +{ +} + +Binding::Binding(QString name, QString scriptCode, BindingType bindingType) + : Binding(name, + std::make_unique<BindingValue>(std::shared_ptr<ScriptExpression>(new ScriptExpression( + scriptCode, ScriptExpression::ExpressionType::BindingExpression))), + bindingType) +{ +} + +Binding::Binding(QString name, QmlObject value, BindingType bindingType) + : Binding(name, std::make_unique<BindingValue>(value), bindingType) +{ +} + +Binding::Binding(QString name, QList<QmlObject> value, BindingType bindingType) + : Binding(name, std::make_unique<BindingValue>(value), bindingType) +{ +} + +Binding::Binding(const Binding &o) + : m_bindingType(o.m_bindingType), + m_name(o.m_name), + m_annotations(o.m_annotations), + m_comments(o.m_comments) +{ + if (o.m_value) { + m_value = std::make_unique<BindingValue>(*o.m_value); + } +} + +Binding::~Binding() { } + +Binding &Binding::operator=(const Binding &o) +{ + m_name = o.m_name; + m_bindingType = o.m_bindingType; + m_annotations = o.m_annotations; + m_comments = o.m_comments; + if (o.m_value) { + if (!m_value) + m_value = std::make_unique<BindingValue>(*o.m_value); + else + *m_value = *o.m_value; + } else { + m_value.reset(); + } + return *this; +} + +bool Binding::iterateDirectSubpaths(DomItem &self, DirectVisitor visitor) +{ + bool cont = true; + cont = cont && self.dvValueField(visitor, Fields::name, m_name); + cont = cont && self.dvValueField(visitor, Fields::isSignalHandler, isSignalHandler()); + if (!m_value) + cont = cont && visitor(PathEls::Field(Fields::value), []() { return DomItem(); }); + else + cont = cont && self.dvItemField(visitor, Fields::value, [this, &self]() { + return m_value->value(self); + }); + cont = cont && self.dvValueField(visitor, Fields::bindingType, int(m_bindingType)); + cont = cont && self.dvWrapField(visitor, Fields::comments, m_comments); + cont = cont && self.dvValueLazyField(visitor, Fields::preCode, [this]() { + return this->preCode(); + }); + cont = cont && self.dvValueLazyField(visitor, Fields::postCode, [this]() { + return this->postCode(); + }); + cont = cont && self.dvWrapField(visitor, Fields::annotations, m_annotations); + return cont; +} + +DomItem Binding::valueItem(DomItem &self) const +{ + if (!m_value) + return DomItem(); + return m_value->value(self); +} + +BindingValueKind Binding::valueKind() const +{ + if (!m_value) + return BindingValueKind::Empty; + return m_value->kind; +} + +QmlObject const *Binding::objectValue() const +{ + if (valueKind() == BindingValueKind::Object) + return &(m_value->object); + return nullptr; +} + +QmlObject *Binding::objectValue() +{ + if (valueKind() == BindingValueKind::Object) + return &(m_value->object); + return nullptr; +} + +QList<QmlObject> const *Binding::arrayValue() const +{ + if (valueKind() == BindingValueKind::Array) + return &(m_value->array); + return nullptr; +} + +QList<QmlObject> *Binding::arrayValue() +{ + if (valueKind() == BindingValueKind::Array) + return &(m_value->array); + return nullptr; +} + +std::shared_ptr<ScriptExpression> Binding::scriptExpressionValue() const +{ + if (valueKind() == BindingValueKind::ScriptExpression) + return m_value->scriptExpression; + return nullptr; +} + +std::shared_ptr<ScriptExpression> Binding::scriptExpressionValue() +{ + if (valueKind() == BindingValueKind::ScriptExpression) + return m_value->scriptExpression; + return nullptr; +} + +Path Binding::addAnnotation(Path selfPathFromOwner, const QmlObject &annotation, QmlObject **aPtr) +{ + return appendUpdatableElementInQList(selfPathFromOwner.field(Fields::annotations), + m_annotations, annotation, aPtr); +} + +void Binding::updatePathFromOwner(Path newPath) +{ + Path base = newPath.field(Fields::annotations); + if (m_value) + m_value->updatePathFromOwner(newPath.field(Fields::value)); + updatePathFromOwnerQList(m_annotations, newPath.field(Fields::annotations)); +} + +bool QmltypesComponent::iterateDirectSubpaths(DomItem &self, DirectVisitor visitor) +{ + bool cont = Component::iterateDirectSubpaths(self, visitor); + cont = cont && self.dvWrapField(visitor, Fields::exports, m_exports); + cont = cont && self.dvValueField(visitor, Fields::metaRevisions, m_metaRevisions); + if (!fileName().isEmpty()) + cont = cont && self.dvValueField(visitor, Fields::fileName, fileName()); // remove? + return cont; +} + +Export Export::fromString(Path source, QStringView exp, Path typePath, ErrorHandler h) +{ + Export res; + res.exportSourcePath = source; + res.typePath = typePath; + int slashIdx = exp.indexOf(QLatin1Char('/')); + int spaceIdx = exp.indexOf(QLatin1Char(' ')); + if (spaceIdx == -1) + spaceIdx = exp.length(); + else + res.version = Version::fromString(exp.mid(spaceIdx + 1)); + if (!res.version.isValid()) + domParsingErrors() + .error(tr("Expected string literal to contain 'Package/Name major.minor' " + "or 'Name major.minor' not '%1'.") + .arg(exp)) + .handle(h); + QString package; + if (slashIdx != -1) + res.uri = exp.left(slashIdx).toString(); + res.typeName = exp.mid(slashIdx + 1, spaceIdx - (slashIdx + 1)).toString(); + return res; +} + +bool AttributeInfo::iterateDirectSubpaths(DomItem &self, DirectVisitor visitor) +{ + bool cont = true; + cont = cont && self.dvValueField(visitor, Fields::name, name); + cont = cont && self.dvValueField(visitor, Fields::access, int(access)); + cont = cont && self.dvValueField(visitor, Fields::typeName, typeName); + cont = cont && self.dvValueField(visitor, Fields::isReadonly, isReadonly); + cont = cont && self.dvValueField(visitor, Fields::isList, isList); + cont = cont && self.dvWrapField(visitor, Fields::comments, comments); + cont = cont && self.dvWrapField(visitor, Fields::annotations, annotations); + return cont; +} + +Path AttributeInfo::addAnnotation(Path selfPathFromOwner, const QmlObject &annotation, + QmlObject **aPtr) +{ + return appendUpdatableElementInQList(selfPathFromOwner.field(Fields::annotations), annotations, + annotation, aPtr); +} + +void AttributeInfo::updatePathFromOwner(Path newPath) +{ + Path base = newPath.field(Fields::annotations); + updatePathFromOwnerQList(annotations, newPath.field(Fields::annotations)); +} + +bool EnumDecl::iterateDirectSubpaths(DomItem &self, DirectVisitor visitor) +{ + bool cont = CommentableDomElement::iterateDirectSubpaths(self, visitor); + cont = cont && self.dvValueField(visitor, Fields::name, name()); + cont = cont && self.dvWrapField(visitor, Fields::values, m_values); + cont = cont && self.dvWrapField(visitor, Fields::annotations, m_annotations); + return cont; +} + +void EnumDecl::updatePathFromOwner(Path newPath) +{ + DomElement::updatePathFromOwner(newPath); + updatePathFromOwnerQList(m_annotations, newPath.field(Fields::annotations)); +} + +QList<QmlObject> EnumDecl::annotations() const +{ + return m_annotations; +} + +void EnumDecl::setAnnotations(QList<QmlObject> annotations) +{ + m_annotations = annotations; +} + +Path EnumDecl::addAnnotation(const QmlObject &annotation, QmlObject **aPtr) +{ + return appendUpdatableElementInQList(pathFromOwner().field(Fields::annotations), m_annotations, + annotation, aPtr); +} + +QList<Path> ImportScope::allSources(DomItem &self) const +{ + DomItem env = self.environment(); + Path selfPath = self.canonicalPath().field(Fields::allSources); + RefCacheEntry cached = RefCacheEntry::forPath(env, selfPath); + if (cached.cached == RefCacheEntry::Cached::All) + return cached.canonicalPaths; + QList<Path> res; + QSet<Path> knownPaths; + QList<Path> toDo(m_importSourcePaths.rbegin(), m_importSourcePaths.rend()); + while (!toDo.isEmpty()) { + Path pNow = toDo.takeLast(); + if (knownPaths.contains(pNow)) + continue; + knownPaths.insert(pNow); + res.append(pNow); + DomItem sourceBase = env.path(pNow); + for (DomItem autoExp : sourceBase.field(Fields::autoExports).values()) { + if (const ModuleAutoExport *autoExpPtr = autoExp.as<ModuleAutoExport>()) { + Path newSource; + if (autoExpPtr->inheritVersion) { + Version v = autoExpPtr->import.version; + DomItem sourceVersion = sourceBase.field(Fields::version); + if (const Version *sourceVersionPtr = sourceVersion.as<Version>()) { + if (v.majorVersion < 0) + v.majorVersion = sourceVersionPtr->majorVersion; + if (v.minorVersion < 0) + v.minorVersion = sourceVersionPtr->minorVersion; + } else { + qWarning() << "autoExport with inherited version " << autoExp + << " but missing version in source" << pNow; + } + Import toImport(autoExpPtr->import.uri, v); + newSource = toImport.importedPath(); + } else { + newSource = autoExpPtr->import.importedPath(); + } + if (newSource && !knownPaths.contains(newSource)) + toDo.append(newSource); + } else { + qWarning() << "expected ModuleAutoExport not " << autoExp.internalKindStr() + << "looking up autoExports of" << sourceBase; + Q_ASSERT(false); + } + } + } + RefCacheEntry::addForPath(env, selfPath, RefCacheEntry { RefCacheEntry::Cached::All, res }); + return res; +} + +bool ImportScope::iterateDirectSubpaths(DomItem &self, DirectVisitor visitor) +{ + bool cont = true; + cont = cont && self.dvReferencesField(visitor, Fields::importSources, m_importSourcePaths); + cont = cont && self.dvItemField(visitor, Fields::allSources, [this, &self]() -> DomItem { + return self.subListItem(List::fromQList<Path>( + self.pathFromOwner().field(Fields::allSources), allSources(self), + [](DomItem &list, const PathEls::PathComponent &p, const Path &el) { + return list.subDataItem(p, el.toString()); + })); + }); + cont = cont && self.dvWrapField(visitor, Fields::qualifiedImports, m_subImports); + cont = cont && self.dvItemField(visitor, Fields::imported, [this, &self]() -> DomItem { + return self.subMapItem(Map( + self.pathFromOwner().field(Fields::imported), + [this, &self](DomItem &map, QString key) { + return map.subListItem(List::fromQList<DomItem>( + map.pathFromOwner().key(key), importedItemsWithName(self, key), + [](DomItem &, const PathEls::PathComponent &, DomItem &el) { + return el; + })); + }, + [this, &self](DomItem &) { return this->importedNames(self); }, + QLatin1String("List<Export>"))); + }); + return cont; +} + +bool PropertyInfo::iterateDirectSubpaths(DomItem &self, DirectVisitor visitor) +{ + bool cont = true; + cont = cont && self.dvValueField(visitor, Fields::propertyDefs, propertyDefs); + cont = cont && self.dvValueField(visitor, Fields::bindings, bindings); + return cont; +} + +BindingValue::BindingValue() : kind(BindingValueKind::Empty) { } + +BindingValue::BindingValue(const QmlObject &o) : kind(BindingValueKind::Object) +{ + new (&object) QmlObject(o); +} + +BindingValue::BindingValue(std::shared_ptr<ScriptExpression> o) + : kind(BindingValueKind::ScriptExpression) +{ + new (&scriptExpression) std::shared_ptr<ScriptExpression>(o); +} + +BindingValue::BindingValue(const QList<QmlObject> &l) : kind(BindingValueKind::Array) +{ + new (&array) QList<QmlObject>(l); +} + +BindingValue::~BindingValue() +{ + clearValue(); +} + +BindingValue::BindingValue(const BindingValue &o) : kind(o.kind) +{ + switch (o.kind) { + case BindingValueKind::Empty: + break; + case BindingValueKind::Object: + new (&object) QmlObject(o.object); + break; + case BindingValueKind::ScriptExpression: + new (&scriptExpression) std::shared_ptr<ScriptExpression>(o.scriptExpression); + break; + case BindingValueKind::Array: + new (&array) QList<QmlObject>(o.array); + } +} + +BindingValue &BindingValue::operator=(const BindingValue &o) +{ + clearValue(); + kind = o.kind; + switch (o.kind) { + case BindingValueKind::Empty: + break; + case BindingValueKind::Object: + new (&object) QmlObject(o.object); + break; + case BindingValueKind::ScriptExpression: + new (&scriptExpression) std::shared_ptr<ScriptExpression>(o.scriptExpression); + break; + case BindingValueKind::Array: + new (&array) QList<QmlObject>(o.array); + } + return *this; +} + +DomItem BindingValue::value(DomItem &binding) +{ + switch (kind) { + case BindingValueKind::Empty: + break; + case BindingValueKind::Object: + return binding.copy(&object); + case BindingValueKind::ScriptExpression: + return binding.subOwnerItem(PathEls::Field(Fields::value), scriptExpression); + case BindingValueKind::Array: + return binding.subListItem(List::fromQListRef<QmlObject>( + binding.pathFromOwner().field(u"value"), array, + [binding](DomItem &self, const PathEls::PathComponent &, QmlObject &obj) { + return self.copy(&obj); + })); + } + return DomItem(); +} + +void BindingValue::updatePathFromOwner(Path newPath) +{ + switch (kind) { + case BindingValueKind::Empty: + break; + case BindingValueKind::Object: + object.updatePathFromOwner(newPath); + break; + case BindingValueKind::ScriptExpression: + break; + case BindingValueKind::Array: + updatePathFromOwnerQList(array, newPath); + break; + } +} + +void BindingValue::clearValue() +{ + switch (kind) { + case BindingValueKind::Empty: + break; + case BindingValueKind::Object: + object.~QmlObject(); + break; + case BindingValueKind::ScriptExpression: + scriptExpression.~shared_ptr(); + break; + case BindingValueKind::Array: + array.~QList<QmlObject>(); + break; + } + kind = BindingValueKind::Empty; +} + +ScriptExpression::ScriptExpression(const ScriptExpression &e) : OwningItem(e) +{ + QMutexLocker l(mutex()); + m_expressionType = e.m_expressionType; + m_engine = e.m_engine; + m_ast = e.m_ast; + if (m_codeStr.isEmpty()) { + m_code = e.m_code; + } else { + m_codeStr = e.m_codeStr; + m_code = m_codeStr; + } + m_localOffset = e.m_localOffset; + m_astComments = e.m_astComments; +} + +std::shared_ptr<ScriptExpression> ScriptExpression::copyWithUpdatedCode(DomItem &self, + QString code) const +{ + std::shared_ptr<ScriptExpression> copy = makeCopy(self); + DomItem container = self.containingObject(); + QString preCodeStr = container.field(Fields::preCode).value().toString(m_preCode.toString()); + QString postCodeStr = container.field(Fields::postCode).value().toString(m_postCode.toString()); + copy->setCode(code, preCodeStr, postCodeStr); + return copy; +} + +bool ScriptExpression::iterateDirectSubpaths(DomItem &self, DirectVisitor visitor) +{ + bool cont = OwningItem::iterateDirectSubpaths(self, visitor); + cont = cont && self.dvValueField(visitor, Fields::code, code()); + if (!preCode().isEmpty()) + cont = cont + && self.dvValueField(visitor, Fields::preCode, preCode(), + ConstantData::Options::MapIsMap); + if (!postCode().isEmpty()) + cont = cont + && self.dvValueField(visitor, Fields::postCode, postCode(), + ConstantData::Options::MapIsMap); + cont = cont + && self.dvValueLazyField( + visitor, Fields::localOffset, + [this]() { return locationToData(localOffset()); }, + ConstantData::Options::MapIsMap); + cont = cont && self.dvValueLazyField(visitor, Fields::astRelocatableDump, [this]() { + return astRelocatableDump(); + }); + cont = cont && self.dvValueField(visitor, Fields::expressionType, int(expressionType())); + return cont; +} + +class FirstNodeVisitor : public VisitAll +{ +public: + quint32 minStart = 0; + quint32 maxEnd = ~quint32(0); + AST::Node *firstNodeInRange = nullptr; + + FirstNodeVisitor(quint32 minStart = 0, quint32 maxEnd = ~quint32(0)) + : minStart(minStart), maxEnd(maxEnd) + { + } + + bool preVisit(AST::Node *n) override + { + if (!VisitAll::uiKinds().contains(n->kind)) { + quint32 start = n->firstSourceLocation().begin(); + quint32 end = n->lastSourceLocation().end(); + if (!firstNodeInRange && minStart <= start && end <= maxEnd && start < end) + firstNodeInRange = n; + } + return !firstNodeInRange; + } +}; + +AST::Node *firstNodeInRange(AST::Node *n, quint32 minStart = 0, quint32 maxEnd = ~quint32(0)) +{ + FirstNodeVisitor visitor(minStart, maxEnd); + AST::Node::accept(n, &visitor); + return visitor.firstNodeInRange; +} + +void ScriptExpression::setCode(QString code, QString preCode, QString postCode) +{ + m_codeStr = code; + if (!preCode.isEmpty() || !postCode.isEmpty()) + m_codeStr = preCode + code + postCode; + m_code = QStringView(m_codeStr).mid(preCode.length(), code.length()); + m_preCode = QStringView(m_codeStr).mid(0, preCode.length()); + m_postCode = QStringView(m_codeStr).mid(preCode.length() + code.length(), postCode.length()); + m_engine = nullptr; + m_ast = nullptr; + m_localOffset = SourceLocation(); + if (!m_code.isEmpty()) { + IndentInfo preChange(m_preCode, 4); + m_localOffset.offset = m_preCode.length(); + m_localOffset.length = m_code.length(); + m_localOffset.startColumn = preChange.trailingString.length(); + m_localOffset.startLine = preChange.nNewlines; + m_engine = std::shared_ptr<QQmlJS::Engine>(new QQmlJS::Engine); + m_astComments = std::shared_ptr<AstComments>(new AstComments(m_engine)); + QQmlJS::Lexer lexer(m_engine.get()); + lexer.setCode(m_codeStr, /*lineno = */ 1, /*qmlMode=*/true); + QQmlJS::Parser parser(m_engine.get()); + if (!parser.parseScript()) + addErrorLocal(domParsingErrors().error(tr("Parsing of code failed"))); + for (DiagnosticMessage msg : parser.diagnosticMessages()) { + ErrorMessage err = domParsingErrors().errorMessage(msg); + err.location.offset -= m_localOffset.offset; + err.location.startLine -= m_localOffset.startLine; + if (err.location.startLine == 1) + err.location.startColumn -= m_localOffset.startColumn; + addErrorLocal(err); + } + m_ast = parser.rootNode(); + if (AST::Program *programPtr = AST::cast<AST::Program *>(m_ast)) { + m_ast = programPtr->statements; + } + if (!m_preCode.isEmpty()) + m_ast = firstNodeInRange(m_ast, m_preCode.length(), + m_preCode.length() + m_code.length()); + if (m_expressionType != ExpressionType::FunctionBody) { + if (AST::StatementList *sList = AST::cast<AST::StatementList *>(m_ast)) { + if (!sList->next) + m_ast = sList->statement; + } + } + AstComments::collectComments(m_engine, m_ast, m_astComments, MutableDomItem(), nullptr); + } +} + +void ScriptExpression::astDumper(Sink s, AstDumperOptions options) const +{ + astNodeDumper(s, ast(), options, 1, 0, [this](SourceLocation astL) { + SourceLocation l = this->locationToLocal(astL); + return this->code().mid(l.offset, l.length); + }); +} + +QString ScriptExpression::astRelocatableDump() const +{ + return dumperToString([this](Sink s) { + this->astDumper(s, AstDumperOption::NoLocations | AstDumperOption::SloppyCompare); + }); +} + +SourceLocation ScriptExpression::globalLocation(DomItem &self) const +{ + if (const FileLocations *fLocPtr = FileLocations::fileLocationsPtr(self)) { + return fLocPtr->regions.value(QString(), fLocPtr->fullRegion); + } + return SourceLocation(); +} + +bool MethodInfo::iterateDirectSubpaths(DomItem &self, DirectVisitor visitor) +{ + bool cont = AttributeInfo::iterateDirectSubpaths(self, visitor); + cont = cont && self.dvWrapField(visitor, Fields::parameters, parameters); + cont = cont && self.dvValueField(visitor, Fields::methodType, int(methodType)); + if (!typeName.isEmpty()) + cont = cont && self.dvReferenceField(visitor, Fields::type, typePath(self)); + if (methodType == MethodType::Method) { + cont = cont && self.dvValueField(visitor, Fields::preCode, preCode(self)); + cont = cont && self.dvValueField(visitor, Fields::postCode, postCode(self)); + } + if (body) + cont = cont && self.dvWrapField(visitor, Fields::body, body); + return cont; +} + +QString MethodInfo::preCode(DomItem &) const +{ + return QLatin1String("function(){"); +} + +QString MethodInfo::postCode(DomItem &) const +{ + return QLatin1String("\n}\n"); +} + +bool MethodParameter::iterateDirectSubpaths(DomItem &self, DirectVisitor visitor) +{ + bool cont = true; + cont = cont && self.dvValueField(visitor, Fields::name, name); + if (!typeName.isEmpty()) { + cont = cont + && self.dvReferenceField(visitor, Fields::type, Paths::lookupCppTypePath(typeName)); + cont = cont && self.dvValueField(visitor, Fields::typeName, typeName); + } + cont = cont && self.dvValueField(visitor, Fields::isPointer, isPointer); + cont = cont && self.dvValueField(visitor, Fields::isReadonly, isReadonly); + cont = cont && self.dvValueField(visitor, Fields::isList, isList); + cont = cont && self.dvWrapField(visitor, Fields::defaultValue, defaultValue); + if (!annotations.isEmpty()) + cont = cont && self.dvWrapField(visitor, Fields::annotations, annotations); + cont = cont && self.dvWrapField(visitor, Fields::comments, comments); + return cont; +} + +bool EnumItem::iterateDirectSubpaths(DomItem &self, DirectVisitor visitor) +{ + bool cont = true; + cont = cont && self.dvValueField(visitor, Fields::name, name()); + cont = cont && self.dvValueField(visitor, Fields::value, value()); + cont = cont && self.dvWrapField(visitor, Fields::comments, m_comments); + return cont; +} + +} // end namespace Dom +} // end namespace QQmlJS + +QT_END_NAMESPACE |