qtdeclarative/src/qmldom/qqmldomelements.cpp

2179 lines
79 KiB
C++
Raw Normal View History

// Copyright (C) 2021 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
Suppress GCC11 warning about maybe-uninitialized data access I don't *think* the compiler is correct. It's complaining that the copy-construction of PathComponent in the lambda here: bool dvReference(DirectVisitor visitor, const PathEls::PathComponent &c, Path referencedObject) { return dvItem(visitor, c, [c, this, referencedObject]() { return this->subReferenceItem(c, referencedObject); }); } which was created as a temporary in dvReferenceField: return dvReference(visitor, PathEls::Field(f), referencedObject); could be copying garbage. Note that the warning is about copying a PathComponent::Data of kind Key, whereas this inline sequence is of Field. Of course, the whole PathComponent::Data is a major ill-advised disaster waiting to happen... In copy constructor ‘QArrayDataPointer<T>::QArrayDataPointer(const QArrayDataPointer<T>&) [with T = char16_t]’, inlined from ‘QString::QString(const QString&)’ at qstring.h:1232:58, inlined from ‘QQmlJS::Dom::PathEls::Key::Key(const QQmlJS::Dom::PathEls::Key&)’ at src/qmldom/qqmldompath_p.h:164:7, inlined from ‘QQmlJS::Dom::PathEls::PathComponent::Data::Data(const QQmlJS::Dom::PathEls::PathComponent::Data&)’ at src/qmldom/qqmldompath_p.h:369:37, inlined from ‘QQmlJS::Dom::PathEls::PathComponent::PathComponent(const QQmlJS::Dom::PathEls::PathComponent&)’ at src/qmldom/qqmldompath_p.h:311:7, inlined from ‘bool QQmlJS::Dom::DomItem::dvReference(QQmlJS::Dom::DirectVisitor, const QQmlJS::Dom::PathEls::PathComponent&, QQmlJS::Dom::Path)’ at src/qmldom/qqmldomitem_p.h:981:22, inlined from ‘bool QQmlJS::Dom::DomItem::dvReferenceField(QQmlJS::Dom::DirectVisitor, QStringView, QQmlJS::Dom::Path)’ at src/qmldom/qqmldomitem_p.h:991:27: qarraydatapointer.h:69:50: error: ‘*(const QArrayDataPointer<char16_t>*)((char*)&<unnamed> + offsetof(const QQmlJS::Dom::PathEls::PathComponent, QQmlJS::Dom::PathEls::PathComponent::data) + 8).QArrayDataPointer<char16_t>::size’ may be used uninitialized [-Werror=maybe-uninitialized] 69 | : d(other.d), ptr(other.ptr), size(other.size) | ~~~~~~^~~~ In file included from src/qmldom/qqmldomelements_p.h:52, from src/qmldom/qqmldomelements.cpp:38: src/qmldom/qqmldomitem_p.h: In member function ‘bool QQmlJS::Dom::DomItem::dvReferenceField(QQmlJS::Dom::DirectVisitor, QStringView, QQmlJS::Dom::Path)’: src/qmldom/qqmldomitem_p.h:991:27: note: ‘<anonymous>’ declared here 991 | return dvReference(visitor, PathEls::Field(f), referencedObject); | ~~~~~~~~~~~^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Change-Id: Iddb933f281024939b6acfffd1689d2fdea0adc61 Reviewed-by: Fabian Kosmale <fabian.kosmale@qt.io>
2021-06-18 23:58:21 +00:00
// Suppress GCC 11 warning about maybe-uninitialized copy of
// another Data. We're not sure if the compiler is actually right,
// but in this type of warning, it often isn't.
//#if defined(Q_CC_GNU) && Q_CC_GNU >= 1100
//QT_WARNING_DISABLE_GCC("-Wmaybe-uninitialized")
#include "qqmldomconstants_p.h"
Dom: Introduce scriptelements in the DOM representation Implement the Dom representation for some constructs, like: * Literals (string and numbers) * IdentifierExpressions (variableNames appearing in expressions) * VariableDeclarations and VariableDeclarationEntries * ForStatements * BlockStatements * IfStatements Introduce the namespace QQmlJS::Dom::ScriptElements that will contain the Dom representation of javascript statements and expressions. To incorporate the new DomItem wrappers in the Dom, extend the qqmldomastcreator to also visit the corresponding javascript AST parts. Like this, it can create the new Dom script elements using its stack: visit() pushes new elements on the stack while endVisit() collects the children of the current element from the stack (note: in the reverse order they were pushed on the stack). The implementation uses two stacks to avoid having to check every time if the top of the stack is a QmlStackElement or a ScriptStackElement, but logically both stacks work as one stack, containing first all QmlStackElements and then all ScriptStackElements. In case non-implemented script elements are encountered by QQmlDomAstCreator, it will disable the script expression construction and print a warning, instead of asserting and making a lot of unrelated tests fail. This mechanism can be removed once all script elements are implemented in the DOM. Wrap script element pointers into a ScriptElementVariant variant that can be used to contain any script element. Added a flag to enable script element construction during DOM construction. Task-number: QTBUG-92876 Change-Id: I24b070ca3ff6ce916036842280cf91a21d049f30 Reviewed-by: Fabian Kosmale <fabian.kosmale@qt.io>
2023-04-05 14:43:50 +00:00
#include "qqmldompath_p.h"
Suppress GCC11 warning about maybe-uninitialized data access I don't *think* the compiler is correct. It's complaining that the copy-construction of PathComponent in the lambda here: bool dvReference(DirectVisitor visitor, const PathEls::PathComponent &c, Path referencedObject) { return dvItem(visitor, c, [c, this, referencedObject]() { return this->subReferenceItem(c, referencedObject); }); } which was created as a temporary in dvReferenceField: return dvReference(visitor, PathEls::Field(f), referencedObject); could be copying garbage. Note that the warning is about copying a PathComponent::Data of kind Key, whereas this inline sequence is of Field. Of course, the whole PathComponent::Data is a major ill-advised disaster waiting to happen... In copy constructor ‘QArrayDataPointer<T>::QArrayDataPointer(const QArrayDataPointer<T>&) [with T = char16_t]’, inlined from ‘QString::QString(const QString&)’ at qstring.h:1232:58, inlined from ‘QQmlJS::Dom::PathEls::Key::Key(const QQmlJS::Dom::PathEls::Key&)’ at src/qmldom/qqmldompath_p.h:164:7, inlined from ‘QQmlJS::Dom::PathEls::PathComponent::Data::Data(const QQmlJS::Dom::PathEls::PathComponent::Data&)’ at src/qmldom/qqmldompath_p.h:369:37, inlined from ‘QQmlJS::Dom::PathEls::PathComponent::PathComponent(const QQmlJS::Dom::PathEls::PathComponent&)’ at src/qmldom/qqmldompath_p.h:311:7, inlined from ‘bool QQmlJS::Dom::DomItem::dvReference(QQmlJS::Dom::DirectVisitor, const QQmlJS::Dom::PathEls::PathComponent&, QQmlJS::Dom::Path)’ at src/qmldom/qqmldomitem_p.h:981:22, inlined from ‘bool QQmlJS::Dom::DomItem::dvReferenceField(QQmlJS::Dom::DirectVisitor, QStringView, QQmlJS::Dom::Path)’ at src/qmldom/qqmldomitem_p.h:991:27: qarraydatapointer.h:69:50: error: ‘*(const QArrayDataPointer<char16_t>*)((char*)&<unnamed> + offsetof(const QQmlJS::Dom::PathEls::PathComponent, QQmlJS::Dom::PathEls::PathComponent::data) + 8).QArrayDataPointer<char16_t>::size’ may be used uninitialized [-Werror=maybe-uninitialized] 69 | : d(other.d), ptr(other.ptr), size(other.size) | ~~~~~~^~~~ In file included from src/qmldom/qqmldomelements_p.h:52, from src/qmldom/qqmldomelements.cpp:38: src/qmldom/qqmldomitem_p.h: In member function ‘bool QQmlJS::Dom::DomItem::dvReferenceField(QQmlJS::Dom::DirectVisitor, QStringView, QQmlJS::Dom::Path)’: src/qmldom/qqmldomitem_p.h:991:27: note: ‘<anonymous>’ declared here 991 | return dvReference(visitor, PathEls::Field(f), referencedObject); | ~~~~~~~~~~~^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Change-Id: Iddb933f281024939b6acfffd1689d2fdea0adc61 Reviewed-by: Fabian Kosmale <fabian.kosmale@qt.io>
2021-06-18 23:58:21 +00:00
#if defined(__GNUC__) && __GNUC__ >= 11
# pragma GCC diagnostic ignored "-Wmaybe-uninitialized"
#endif
#include "qqmldomelements_p.h"
#include "qqmldomcomments_p.h"
#include "qqmldomastdumper_p.h"
#include "qqmldommock_p.h"
#include "qqmldomreformatter_p.h"
#include "qqmldomoutwriter_p.h"
#include "qqmldomlinewriter_p.h"
#include "qqmldomtop_p.h"
#include "qqmldomexternalitems_p.h"
#include <QtQml/private/qqmljslexer_p.h>
#include <QtQml/private/qqmljsparser_p.h>
#include <QtQml/private/qqmljsengine_p.h>
#include <QtQml/private/qqmljsastvisitor_p.h>
#include <QtQml/private/qqmljsast_p.h>
#include <QtCore/QScopeGuard>
#include <QtCore/QRegularExpression>
#include <QtCore/QDir>
#include <QtCore/QBasicMutex>
#include <QtCore/QUrl>
#include <optional>
#include <limits>
QT_BEGIN_NAMESPACE
using namespace Qt::StringLiterals;
namespace QQmlJS {
namespace Dom {
namespace Paths {
Path moduleIndexPath(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();
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)
{
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(const DomItem &self, DirectVisitor visitor) const
{
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(const DomItem &self, DirectVisitor visitor) const
{
bool cont = CommentableDomElement::iterateDirectSubpaths(self, visitor);
cont = cont && self.dvValueField(visitor, Fields::name, name());
cont = cont && self.dvWrapField(visitor, Fields::enumerations, m_enumerations);
cont = cont && self.dvWrapField(visitor, Fields::objects, m_objects);
cont = cont && self.dvValueField(visitor, Fields::isSingleton, isSingleton());
cont = cont && self.dvValueField(visitor, Fields::isCreatable, isCreatable());
cont = cont && self.dvValueField(visitor, Fields::isComposite, isComposite());
cont = cont && self.dvValueField(visitor, Fields::attachedTypeName, attachedTypeName());
cont = cont && self.dvReferenceField(visitor, Fields::attachedType, attachedTypePath(self));
return cont;
}
DomItem Component::field(const DomItem &self, QStringView name) const
{
switch (name.size()) {
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(const DomItem &self, DirectVisitor visitor) const
{
bool cont = Component::iterateDirectSubpaths(self, visitor);
cont = cont && self.dvWrapField(visitor, Fields::ids, m_ids);
cont = cont && self.dvValueLazyField(visitor, Fields::subComponents, [this, &self]() {
return this->subComponents(self);
});
return cont;
}
void QmlComponent::updatePathFromOwner(Path newPath)
{
Component::updatePathFromOwner(newPath);
updatePathFromOwnerMultiMap(m_ids, newPath.field(Fields::annotations));
}
void QmlComponent::writeOut(const DomItem &self, OutWriter &lw) const
{
if (name().contains(QLatin1Char('.'))) {
// inline component
lw.ensureNewline()
.writeRegion(ComponentKeywordRegion)
.space()
.writeRegion(IdentifierRegion, name().split(QLatin1Char('.')).last())
.writeRegion(ColonTokenRegion)
.space();
}
self.field(Fields::objects).index(0).writeOut(lw);
}
QList<QString> QmlComponent::subComponentsNames(const DomItem &self) const
{
DomItem components = self.owner().field(Fields::components);
const QSet<QString> cNames = components.keys();
QString myNameDot = self.pathFromOwner()[1].headName();
if (!myNameDot.isEmpty())
myNameDot += QLatin1Char('.');
QList<QString> subNames;
for (const QString &cName : cNames)
if (cName.startsWith(myNameDot)
&& !QStringView(cName).mid(myNameDot.size()).contains(QLatin1Char('.'))
&& !cName.isEmpty())
subNames.append(cName);
std::sort(subNames.begin(), subNames.end());
return subNames;
}
QList<DomItem> QmlComponent::subComponents(const DomItem &self) const
{
DomItem components = self.owner().field(Fields::components);
QList<DomItem> res;
for (const QString &cName : subComponentsNames(self))
for (const DomItem &comp : components.key(cName).values())
res.append(comp);
return res;
}
Version Version::fromString(QStringView v)
{
if (v.isEmpty())
return Version(Latest, Latest);
QRegularExpression r(
QRegularExpression::anchoredPattern(QStringLiteral(uR"(([0-9]*)(?:\.([0-9]*))?)")));
auto m = r.matchView(v);
if (m.hasMatch()) {
bool ok;
int majorV = m.capturedView(1).toInt(&ok);
if (!ok)
majorV = Version::Undefined;
int minorV = m.capturedView(2).toInt(&ok);
if (!ok)
minorV = Version::Undefined;
return Version(majorV, minorV);
}
return {};
}
Version::Version(qint32 majorV, qint32 minorV) : majorVersion(majorV), minorVersion(minorV) { }
bool Version::isLatest() const
{
return majorVersion == Latest && minorVersion == Latest;
}
bool Version::isValid() const
{
return majorVersion >= 0 && minorVersion >= 0;
}
QString Version::stringValue() const
{
if (isLatest())
return QString();
if (minorVersion < 0) {
if (majorVersion < 0)
return QLatin1String(".");
else
return QString::number(majorVersion);
}
if (majorVersion < 0)
return QLatin1String(".") + QString::number(minorVersion);
return QString::number(majorVersion) + QChar::fromLatin1('.') + QString::number(minorVersion);
}
bool Version::iterateDirectSubpaths(const DomItem &self, DirectVisitor visitor) const
{
bool cont = true;
cont = cont && self.dvWrapField(visitor, Fields::majorVersion, majorVersion);
cont = cont && self.dvWrapField(visitor, Fields::minorVersion, minorVersion);
cont = cont && self.dvValueField(visitor, Fields::isLatest, isLatest());
cont = cont && self.dvValueField(visitor, Fields::isValid, isValid());
cont = cont && self.dvValueLazyField(visitor, Fields::stringValue, [this]() {
return this->stringValue();
});
return cont;
}
QRegularExpression Import::importRe()
{
static QRegularExpression res(QRegularExpression::anchoredPattern(QStringLiteral(
uR"((?<uri>\w+(?:\.\w+)*)(?:\W+(?<version>[0-9]+(?:\.[0-9]*)?))?(?:\W+as\W+(?<id>\w+))?$)")));
return res;
}
Import Import::fromUriString(QString importStr, Version v, QString importId, ErrorHandler handler)
{
auto m = importRe().match(importStr);
if (m.hasMatch()) {
if (v.majorVersion == Version::Undefined && v.minorVersion == Version::Undefined)
v = Version::fromString(m.captured(2));
else if (!m.captured(u"version").isEmpty())
domParsingErrors()
.warning(tr("Version %1 in import string '%2' overridden by explicit "
"version %3")
.arg(m.captured(2), importStr, v.stringValue()))
.handle(handler);
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(QmlUri::fromUriString(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 importId, ErrorHandler)
{
return Import(QmlUri::fromDirectoryString(importStr), Version(), importId);
}
bool Import::iterateDirectSubpaths(const DomItem &self, DirectVisitor visitor) const
{
bool cont = true;
cont = cont && self.dvValueField(visitor, Fields::uri, uri.toString());
cont = cont && self.dvWrapField(visitor, Fields::version, version);
if (!importId.isEmpty())
cont = cont && self.dvValueField(visitor, Fields::importId, importId);
if (implicit)
cont = cont && self.dvValueField(visitor, Fields::implicit, implicit);
cont = cont && self.dvWrapField(visitor, Fields::comments, comments);
return cont;
}
void Import::writeOut(const DomItem &self, OutWriter &ow) const
{
if (implicit)
return;
QString code;
const DomItem owner = self.owner();
if (std::shared_ptr<QmlFile> qmlFilePtr = self.ownerAs<QmlFile>())
code = qmlFilePtr->code();
// check for an empty line before the import, and preserve it
int preNewlines = 0;
const FileLocations::Tree elLoc = FileLocations::findAttachedInfo(self).foundTree;
quint32 start = elLoc->info().fullRegion.offset;
if (size_t(code.size()) >= start) {
while (start != 0) {
QChar c = code.at(--start);
if (c == u'\n') {
if (++preNewlines == 2)
break;
} else if (!c.isSpace())
break;
}
}
if (preNewlines == 0)
++preNewlines;
ow.ensureNewline(preNewlines);
ow.writeRegion(ImportTokenRegion).space();
ow.writeRegion(ImportUriRegion, uri.toString());
if (uri.isModule()) {
QString vString = version.stringValue();
if (!vString.isEmpty())
ow.space().write(vString);
}
if (!importId.isEmpty())
ow.space().writeRegion(AsTokenRegion).space().writeRegion(IdNameRegion, importId);
}
Id::Id(QString idName, Path referredObject) : name(idName), referredObjectPath(referredObject) { }
bool Id::iterateDirectSubpaths(const DomItem &self, DirectVisitor visitor) const
{
bool cont = true;
cont = cont && self.dvValueField(visitor, Fields::name, name);
cont = cont && self.dvReferenceField(visitor, Fields::referredObject, referredObjectPath);
cont = cont && self.dvWrapField(visitor, Fields::comments, comments);
cont = cont && self.dvWrapField(visitor, Fields::annotations, annotations);
cont = cont && self.dvWrapField(visitor, Fields::value, value);
return cont;
}
void Id::updatePathFromOwner(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(const DomItem &self, DirectVisitor visitor) const
{
bool cont = CommentableDomElement::iterateDirectSubpaths(self, visitor);
if (!idStr().isEmpty())
cont = cont && self.dvValueField(visitor, Fields::idStr, idStr());
cont = cont && self.dvValueField(visitor, Fields::name, name());
if (!prototypePaths().isEmpty())
cont = cont && self.dvReferencesField(visitor, Fields::prototypes, m_prototypePaths);
if (nextScopePath())
cont = cont && self.dvReferenceField(visitor, Fields::nextScope, nextScopePath());
cont = cont && self.dvWrapField(visitor, Fields::propertyDefs, m_propertyDefs);
cont = cont && self.dvWrapField(visitor, Fields::bindings, m_bindings);
cont = cont && self.dvWrapField(visitor, Fields::methods, m_methods);
cont = cont && self.dvWrapField(visitor, Fields::children, m_children);
cont = cont && self.dvWrapField(visitor, Fields::annotations, m_annotations);
cont = cont && self.dvItemField(visitor, Fields::propertyInfos, [this, &self]() {
return self.subMapItem(Map(
pathFromOwner().field(Fields::propertyInfos),
[&self](const DomItem &map, QString k) {
auto pInfo = self.propertyInfoWithName(k);
return map.wrap(PathEls::Key(k), pInfo);
},
[&self](const 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(const DomItem &self, DirectVisitor visitor) const
{
bool cont = iterateBaseDirectSubpaths(self, visitor);
cont = cont && self.dvValueLazyField(visitor, Fields::defaultPropertyName, [this, &self]() {
return defaultPropertyName(self);
});
return cont;
}
DomItem QmlObject::field(const DomItem &self, QStringView name) const
{
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)
// Need to explicitly copy self here since we might store this and call it later.
return self.subMapItem(Map(
pathFromOwner().field(Fields::propertyInfos),
[copiedSelf = self](const DomItem &map, const QString &k) {
return map.wrap(PathEls::Key(k), copiedSelf.propertyInfoWithName(k));
},
[copiedSelf = self](const DomItem &) { return copiedSelf.propertyInfoNames(); },
QLatin1String("PropertyInfo")));
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(const DomItem &self) const
{
QString dProp = localDefaultPropertyName();
if (!dProp.isEmpty())
return dProp;
QString res = QStringLiteral(u"data");
self.visitPrototypeChain(
[&res](const DomItem &obj) {
if (const QmlObject *objPtr = obj.as<QmlObject>()) {
QString dProp = objPtr->localDefaultPropertyName();
if (!dProp.isEmpty()) {
res = dProp;
return false;
}
}
return true;
},
VisitPrototypesOption::SkipFirst);
return res;
}
bool QmlObject::iterateSubOwners(const DomItem &self, function_ref<bool(const DomItem &)> visitor) const
{
bool cont = self.field(Fields::bindings).visitKeys([visitor](QString, const DomItem &bs) {
return bs.visitIndexes([visitor](const DomItem &b) {
DomItem v = b.field(Fields::value);
if (std::shared_ptr<ScriptExpression> vPtr = v.ownerAs<ScriptExpression>()) {
if (!visitor(v))
return false;
return v.iterateSubOwners(visitor); // currently not needed, avoid?
}
return true;
});
});
cont = cont && self.field(Fields::children).visitIndexes([visitor](const DomItem &qmlObj) {
if (const QmlObject *qmlObjPtr = qmlObj.as<QmlObject>()) {
return qmlObjPtr->iterateSubOwners(qmlObj, visitor);
}
Q_ASSERT(false);
return true;
});
return cont;
}
static QStringList dotExpressionToList(std::shared_ptr<ScriptExpression> expr)
{
QStringList res;
AST::Node *node = (expr ? expr->ast() : nullptr);
while (node) {
switch (node->kind) {
case AST::Node::Kind_IdentifierExpression: {
AST::IdentifierExpression *id = AST::cast<AST::IdentifierExpression *>(node);
res.prepend(id->name.toString());
return res;
}
case AST::Node::Kind_FieldMemberExpression: {
AST::FieldMemberExpression *id = AST::cast<AST::FieldMemberExpression *>(node);
res.prepend(id->name.toString());
node = id->base;
break;
}
default:
qCDebug(writeOutLog).noquote() << "Could not convert dot expression to list for:\n"
<< expr->astRelocatableDump();
return QStringList();
}
}
return res;
}
LocallyResolvedAlias QmlObject::resolveAlias(const DomItem &self,
std::shared_ptr<ScriptExpression> accessSequence) const
{
QStringList accessSequenceList = dotExpressionToList(accessSequence);
return resolveAlias(self, accessSequenceList);
}
LocallyResolvedAlias QmlObject::resolveAlias(const DomItem &self, const QStringList &accessSequence) const
{
LocallyResolvedAlias res;
QSet<QString> visitedAlias;
if (accessSequence.isEmpty()) {
return res;
} else if (accessSequence.size() > 3) {
res.status = LocallyResolvedAlias::Status::TooDeep;
return res;
}
QString idName = accessSequence.first();
DomItem idTarget = self.component()
.field(Fields::ids)
.key(idName)
.index(0)
.field(Fields::referredObject)
.get();
if (!idTarget)
return res;
res.baseObject = idTarget;
res.accessedPath = accessSequence.mid(1);
res.typeName = idTarget.name();
res.status = LocallyResolvedAlias::Status::ResolvedObject;
// check if it refers to locally defined props/objs
while (!res.accessedPath.isEmpty()) {
QString pNow = res.accessedPath.first();
DomItem defNow = res.baseObject.propertyDefs().key(pNow).index(0);
if (const PropertyDefinition *defNowPtr = defNow.as<PropertyDefinition>()) {
if (defNowPtr->isAlias()) {
res.typeName = QString();
++res.nAliases;
QString aliasPath = defNow.canonicalPath().toString();
if (visitedAlias.contains(aliasPath)) {
res.status = LocallyResolvedAlias::Status::Loop;
return res;
}
visitedAlias.insert(aliasPath);
DomItem valNow = res.baseObject.bindings().key(pNow).index(0);
if (std::shared_ptr<ScriptExpression> exp =
valNow.field(Fields::value).ownerAs<ScriptExpression>()) {
QStringList expList = dotExpressionToList(exp);
if (expList.isEmpty()) {
res.status = LocallyResolvedAlias::Status::Invalid;
return res;
} else if (expList.size() > 3) {
res.status = LocallyResolvedAlias::Status::TooDeep;
return res;
}
idName = expList.first();
idTarget = self.component()
.field(Fields::ids)
.key(idName)
.index(0)
.field(Fields::referredObject)
.get();
res.baseObject = idTarget;
res.accessedPath = expList.mid(1) + res.accessedPath.mid(1);
if (!idTarget) {
res.status = LocallyResolvedAlias::Status::Invalid;
return res;
}
res.status = LocallyResolvedAlias::Status::ResolvedObject;
res.typeName = idTarget.name();
} else {
res.status = LocallyResolvedAlias::Status::Invalid;
return res;
}
} else {
res.localPropertyDef = defNow;
res.typeName = defNowPtr->typeName;
res.accessedPath = res.accessedPath.mid(1);
DomItem valNow = res.baseObject.bindings().key(pNow).index(0).field(Fields::value);
if (valNow.internalKind() == DomType::QmlObject) {
res.baseObject = valNow;
res.typeName = valNow.name();
res.status = LocallyResolvedAlias::Status::ResolvedObject;
} else {
res.status = LocallyResolvedAlias::Status::ResolvedProperty;
return res;
}
}
} else {
return res;
}
}
return res;
}
MutableDomItem QmlObject::addPropertyDef(
MutableDomItem &self, const PropertyDefinition &propertyDef, AddOption option)
{
Path p = addPropertyDef(propertyDef, option);
if (p.last().headIndex(0) > 1)
self.owningItemPtr()->addErrorLocal(domParsingErrors().error(
tr("Repeated PropertyDefinition with name %1").arg(propertyDef.name)));
return self.owner().path(p);
}
MutableDomItem QmlObject::addBinding(MutableDomItem &self, Binding binding, AddOption option)
{
Path p = addBinding(binding, option);
if (p && p.last().headIndex(0) > 1)
self.owningItemPtr()->addErrorLocal(
domParsingErrors().error(tr("Repeated binding with name %1").arg(binding.name())));
return self.owner().path(p);
}
MutableDomItem QmlObject::addMethod(
MutableDomItem &self, const MethodInfo &functionDef, AddOption option)
{
Path p = addMethod(functionDef, option);
if (p.last().headIndex(0) > 1)
self.owningItemPtr()->addErrorLocal(
domParsingErrors().error(tr("Repeated Method with name %1").arg(functionDef.name)));
return self.owner().path(p);
}
void QmlObject::writeOut(const DomItem &self, OutWriter &ow, QString onTarget) const
{
const quint32 posOfNewElements = std::numeric_limits<quint32>::max();
bool isRootObject = pathFromOwner().length() == 5
&& pathFromOwner()[0] == Path::Field(Fields::components)
&& pathFromOwner()[3] == Path::Field(Fields::objects);
QString code;
DomItem owner = self.owner();
if (std::shared_ptr<QmlFile> qmlFilePtr = self.ownerAs<QmlFile>())
code = qmlFilePtr->code();
ow.writeRegion(IdentifierRegion, name());
if (!onTarget.isEmpty())
ow.space().writeRegion(OnTokenRegion).space().writeRegion(OnTargetRegion, onTarget);
ow.writeRegion(LeftBraceRegion, u" {").newline();
int baseIndent = ow.increaseIndent();
int spacerId = 0;
if (!idStr().isEmpty()) { // *always* put id first
DomItem myId = self.component().field(Fields::ids).key(idStr()).index(0);
if (myId)
myId.writeOutPre(ow);
ow.ensureNewline()
.writeRegion(IdTokenRegion)
.writeRegion(IdColonTokenRegion)
.space()
.writeRegion(IdNameRegion, idStr());
if (ow.lineWriter.options().attributesSequence
== LineWriterOptions::AttributesSequence::Normalize) {
ow.ensureNewline(2);
}
if (myId)
myId.writeOutPost(ow);
}
quint32 counter = ow.counter();
DomItem component;
if (isRootObject)
component = self.containingObject();
auto startLoc = [&](FileLocations::Tree l) {
if (l)
return l->info().fullRegion;
return SourceLocation(posOfNewElements, 0, 0, 0);
};
if (ow.lineWriter.options().attributesSequence
== LineWriterOptions::AttributesSequence::Preserve) {
QList<QPair<SourceLocation, DomItem>> attribs;
AttachedInfoLookupResult<FileLocations::Tree> objLoc =
FileLocations::findAttachedInfo(self);
FileLocations::Tree componentLoc;
if (isRootObject && objLoc.foundTree)
componentLoc = objLoc.foundTree->parent()->parent();
auto addMMap = [&attribs, &startLoc](const DomItem &base, FileLocations::Tree baseLoc) {
if (!base)
return;
const auto values = base.values();
for (const auto &els : values) {
FileLocations::Tree elsLoc =
FileLocations::find(baseLoc, els.pathFromOwner().last());
const auto elsValues = els.values();
for (const auto &el : elsValues) {
FileLocations::Tree elLoc =
FileLocations::find(elsLoc, el.pathFromOwner().last());
attribs.append(std::make_pair(startLoc(elLoc), el));
}
}
};
auto addMyMMap = [this, &objLoc, &self, &addMMap](QStringView fieldName) {
DomItem base = this->field(self, fieldName);
addMMap(base, FileLocations::find(objLoc.foundTree, base.pathFromOwner().last()));
};
auto addSingleLevel = [&attribs, &startLoc](const DomItem &base, FileLocations::Tree baseLoc) {
if (!base)
return;
const auto baseValues = base.values();
for (const auto &el : baseValues) {
FileLocations::Tree elLoc = FileLocations::find(baseLoc, el.pathFromOwner().last());
attribs.append(std::make_pair(startLoc(elLoc), el));
}
};
if (isRootObject) {
DomItem enums = component.field(Fields::enumerations);
addMMap(enums, FileLocations::find(componentLoc, enums.pathFromOwner().last()));
}
addMyMMap(Fields::propertyDefs);
addMyMMap(Fields::bindings);
addMyMMap(Fields::methods);
DomItem children = field(self, Fields::children);
addSingleLevel(children,
FileLocations::find(objLoc.foundTree, children.pathFromOwner().last()));
if (isRootObject) {
DomItem subCs = component.field(Fields::subComponents);
for (const DomItem &c : subCs.values()) {
AttachedInfoLookupResult<FileLocations::Tree> subLoc =
FileLocations::findAttachedInfo(c);
Q_ASSERT(subLoc.foundTree);
attribs.append(std::make_pair(startLoc(subLoc.foundTree), c));
}
}
std::stable_sort(attribs.begin(), attribs.end(),
[](const std::pair<SourceLocation, DomItem> &el1,
const std::pair<SourceLocation, DomItem> &el2) {
if (el1.first.offset < el2.first.offset)
return true;
if (el1.first.offset > el2.first.offset)
return false;
int i = int(el1.second.internalKind())
- int(el2.second.internalKind());
return i < 0;
});
qsizetype iAttr = 0;
while (iAttr != attribs.size()) {
auto &el = attribs[iAttr++];
// check for an empty line before the current element, and preserve it
int preNewlines = 0;
quint32 start = el.first.offset;
if (start != posOfNewElements && size_t(code.size()) >= start) {
while (start != 0) {
QChar c = code.at(--start);
if (c == u'\n') {
if (++preNewlines == 2)
break;
} else if (!c.isSpace())
break;
}
}
if (preNewlines == 0)
++preNewlines;
ow.ensureNewline(preNewlines);
if (el.second.internalKind() == DomType::PropertyDefinition && iAttr != attribs.size()
&& el.first.offset != ~quint32(0)) {
DomItem b;
auto &bPair = attribs[iAttr];
if (bPair.second.internalKind() == DomType::Binding
&& bPair.first.begin() < el.first.end()
&& bPair.second.name() == el.second.name()) {
b = bPair.second;
++iAttr;
b.writeOutPre(ow);
}
el.second.writeOut(ow);
if (b) {
ow.write(u": ");
if (const Binding *bPtr = b.as<Binding>())
bPtr->writeOutValue(b, ow);
else {
qWarning() << "Internal error casting binding to Binding in"
<< b.canonicalPath();
ow.writeRegion(LeftBraceRegion).writeRegion(RightBraceRegion);
}
b.writeOutPost(ow);
}
} else {
el.second.writeOut(ow);
}
}
ow.decreaseIndent(1, baseIndent);
ow.ensureNewline().write(u"}");
return;
}
DomItem bindings = field(self, Fields::bindings);
DomItem propertyDefs = field(self, Fields::propertyDefs);
if (isRootObject) {
const auto descs = component.field(Fields::enumerations).values();
for (const auto &enumDescs : descs) {
const auto values = enumDescs.values();
for (const auto &enumDesc : values) {
ow.ensureNewline(1);
enumDesc.writeOut(ow);
ow.ensureNewline(1);
}
}
}
if (counter != ow.counter() || !idStr().isEmpty())
spacerId = ow.addNewlinesAutospacerCallback(2);
QSet<QString> mergedDefBinding;
for (const QString &defName : propertyDefs.sortedKeys()) {
const auto pDefs = propertyDefs.key(defName).values();
for (const auto &pDef : pDefs) {
const PropertyDefinition *pDefPtr = pDef.as<PropertyDefinition>();
Q_ASSERT(pDefPtr);
DomItem b;
bool uniqueDeclarationWithThisName = pDefs.size() == 1;
if (uniqueDeclarationWithThisName && !pDefPtr->isRequired)
bindings.key(pDef.name()).visitIndexes([&b, pDefPtr](const DomItem &el) {
const Binding *elPtr = el.as<Binding>();
if (elPtr && elPtr->bindingType() == BindingType::Normal) {
switch (elPtr->valueKind()) {
case BindingValueKind::ScriptExpression:
b = el;
break;
case BindingValueKind::Array:
if (!pDefPtr->isDefaultMember
&& pDefPtr->isParametricType())
b = el;
break;
case BindingValueKind::Object:
if (!pDefPtr->isDefaultMember
&& !pDefPtr->isParametricType())
b = el;
break;
case BindingValueKind::Empty:
break;
}
return false;
}
return true;
});
if (b) {
mergedDefBinding.insert(defName);
b.writeOutPre(ow);
}
pDef.writeOut(ow);
if (b) {
ow.write(u": ");
if (const Binding *bPtr = b.as<Binding>())
bPtr->writeOutValue(b, ow);
else {
qWarning() << "Internal error casting binding to Binding in"
<< b.canonicalPath();
ow.writeRegion(LeftBraceRegion).writeRegion(RightBraceRegion);
}
b.writeOutPost(ow);
}
}
}
ow.removeTextAddCallback(spacerId);
QList<DomItem> signalList, methodList;
const auto fields = field(self, Fields::methods).values();
for (const auto &ms : fields) {
const auto values = ms.values();
for (const auto &m : values) {
const MethodInfo *mPtr = m.as<MethodInfo>();
if (mPtr && mPtr->methodType == MethodInfo::MethodType::Signal)
signalList.append(m);
else
methodList.append(m);
}
}
if (counter != ow.counter())
spacerId = ow.addNewlinesAutospacerCallback(2);
for (const auto &sig : std::as_const(signalList)) {
ow.ensureNewline();
sig.writeOut(ow);
ow.ensureNewline();
}
ow.removeTextAddCallback(spacerId);
if (counter != ow.counter())
spacerId = ow.addNewlinesAutospacerCallback(2);
bool first = true;
for (const auto &method : std::as_const(methodList)) {
if (!first && ow.lineWriter.options().functionsSpacing) {
ow.newline();
}
ow.ensureNewline();
first = false;
method.writeOut(ow);
ow.ensureNewline();
}
ow.removeTextAddCallback(spacerId);
QList<DomItem> normalBindings, signalHandlers, delayedBindings;
for (const auto &bName : bindings.sortedKeys()) {
bool skipFirstNormal = mergedDefBinding.contains(bName);
const auto values = bindings.key(bName).values();
for (const auto &b : values) {
const Binding *bPtr = b.as<Binding>();
if (skipFirstNormal) {
if (bPtr && bPtr->bindingType() == BindingType::Normal) {
skipFirstNormal = false;
continue;
}
}
if (bPtr->valueKind() == BindingValueKind::Array
|| bPtr->valueKind() == BindingValueKind::Object)
delayedBindings.append(b);
else if (b.field(Fields::isSignalHandler).value().toBool(false))
signalHandlers.append(b);
else
normalBindings.append(b);
}
}
if (counter != ow.counter())
spacerId = ow.addNewlinesAutospacerCallback(2);
for (const auto &b : std::as_const(normalBindings))
b.writeOut(ow);
ow.removeTextAddCallback(spacerId);
if (counter != ow.counter())
spacerId = ow.addNewlinesAutospacerCallback(2);
for (const auto &b : std::as_const(delayedBindings))
b.writeOut(ow);
ow.removeTextAddCallback(spacerId);
if (counter != ow.counter())
spacerId = ow.addNewlinesAutospacerCallback(2);
for (const auto &b : std::as_const(signalHandlers))
b.writeOut(ow);
ow.removeTextAddCallback(spacerId);
if (counter != ow.counter())
spacerId = ow.addNewlinesAutospacerCallback(2);
first = true;
const auto values = field(self, Fields::children).values();
for (const auto &c : values) {
if (!first && ow.lineWriter.options().objectsSpacing) {
ow.newline().newline();
}
first = false;
ow.ensureNewline();
c.writeOut(ow);
}
ow.removeTextAddCallback(spacerId);
if (isRootObject) {
// we are a root object, possibly add components
DomItem subComps = component.field(Fields::subComponents);
if (counter != ow.counter())
spacerId = ow.addNewlinesAutospacerCallback(2);
const auto values = subComps.values();
for (const auto &subC : values) {
ow.ensureNewline();
subC.writeOut(ow);
}
ow.removeTextAddCallback(spacerId);
}
ow.decreaseIndent(1, baseIndent);
ow.ensureNewline().write(u"}");
}
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::make_shared<ScriptExpression>(
scriptCode, ScriptExpression::ExpressionType::BindingExpression, 0,
Binding::preCodeForName(name), Binding::postCodeForName(name))),
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),
m_bindingIdentifiers(o.m_bindingIdentifiers)
{
if (o.m_value) {
m_value = std::make_unique<BindingValue>(*o.m_value);
}
}
Binding::~Binding() { }
Binding &Binding::operator=(const Binding &o)
{
m_name = o.m_name;
m_bindingType = o.m_bindingType;
m_annotations = o.m_annotations;
m_comments = o.m_comments;
m_bindingIdentifiers = o.m_bindingIdentifiers;
if (o.m_value) {
if (!m_value)
m_value = std::make_unique<BindingValue>(*o.m_value);
else
*m_value = *o.m_value;
} else {
m_value.reset();
}
return *this;
}
bool Binding::iterateDirectSubpaths(const DomItem &self, DirectVisitor visitor) const
{
bool cont = true;
cont = cont && self.dvValueField(visitor, Fields::name, m_name);
cont = cont && self.dvValueField(visitor, Fields::isSignalHandler, isSignalHandler());
if (!m_value)
cont = cont && visitor(PathEls::Field(Fields::value), []() { return DomItem(); });
else
cont = cont && self.dvItemField(visitor, Fields::value, [this, &self]() {
return m_value->value(self);
});
cont = cont && self.dvValueField(visitor, Fields::bindingType, int(m_bindingType));
cont = cont && self.dvWrapField(visitor, Fields::comments, m_comments);
cont = cont && self.dvValueLazyField(visitor, Fields::preCode, [this]() {
return this->preCode();
});
cont = cont && self.dvValueLazyField(visitor, Fields::postCode, [this]() {
return this->postCode();
});
if (m_bindingIdentifiers) {
cont = cont && self.dvItemField(visitor, Fields::bindingIdentifiers, [this, &self]() {
return self.subScriptElementWrapperItem(m_bindingIdentifiers);
});
}
cont = cont && self.dvWrapField(visitor, Fields::annotations, m_annotations);
return cont;
}
DomItem Binding::valueItem(const DomItem &self) const
{
if (!m_value)
return DomItem();
return m_value->value(self);
}
BindingValueKind Binding::valueKind() const
{
if (!m_value)
return BindingValueKind::Empty;
return m_value->kind;
}
QmlObject const *Binding::objectValue() const
{
if (valueKind() == BindingValueKind::Object)
return &(m_value->object);
return nullptr;
}
QmlObject *Binding::objectValue()
{
if (valueKind() == BindingValueKind::Object)
return &(m_value->object);
return nullptr;
}
QList<QmlObject> const *Binding::arrayValue() const
{
if (valueKind() == BindingValueKind::Array)
return &(m_value->array);
return nullptr;
}
QList<QmlObject> *Binding::arrayValue()
{
if (valueKind() == BindingValueKind::Array)
return &(m_value->array);
return nullptr;
}
std::shared_ptr<ScriptExpression> Binding::scriptExpressionValue() const
{
if (valueKind() == BindingValueKind::ScriptExpression)
return m_value->scriptExpression;
return nullptr;
}
std::shared_ptr<ScriptExpression> Binding::scriptExpressionValue()
{
if (valueKind() == BindingValueKind::ScriptExpression)
return m_value->scriptExpression;
return nullptr;
}
Path Binding::addAnnotation(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));
}
void Binding::writeOut(const DomItem &self, OutWriter &lw) const
{
lw.ensureNewline();
if (m_bindingType == BindingType::Normal) {
lw.writeRegion(IdentifierRegion, name());
lw.writeRegion(ColonTokenRegion).space();
writeOutValue(self, lw);
} else {
DomItem v = valueItem(self);
if (const QmlObject *vPtr = v.as<QmlObject>()) {
v.writeOutPre(lw);
vPtr->writeOut(v, lw, name());
v.writeOutPost(lw);
} else {
qCWarning(writeOutLog()) << "On Binding requires an QmlObject Value, not "
<< v.internalKindStr() << " at " << self.canonicalPath();
}
}
}
void Binding::writeOutValue(const DomItem &self, OutWriter &lw) const
{
DomItem v = valueItem(self);
switch (valueKind()) {
case BindingValueKind::Empty:
qCWarning(writeOutLog()) << "Writing of empty binding " << name();
lw.write(u"{}");
break;
case BindingValueKind::Array:
if (const List *vPtr = v.as<List>()) {
v.writeOutPre(lw);
vPtr->writeOut(v, lw, false);
v.writeOutPost(lw);
}
break;
case BindingValueKind::Object:
case BindingValueKind::ScriptExpression:
v.writeOut(lw);
break;
}
}
bool QmltypesComponent::iterateDirectSubpaths(const DomItem &self, DirectVisitor visitor) const
{
bool cont = Component::iterateDirectSubpaths(self, visitor);
cont = cont && self.dvWrapField(visitor, Fields::exports, m_exports);
cont = cont && self.dvValueField(visitor, Fields::metaRevisions, m_metaRevisions);
if (!fileName().isEmpty())
cont = cont && self.dvValueField(visitor, Fields::fileName, fileName()); // remove?
cont = cont && self.dvValueField(visitor, Fields::interfaceNames, m_interfaceNames);
cont = cont && self.dvValueField(visitor, Fields::hasCustomParser, m_hasCustomParser);
cont = cont && self.dvValueField(visitor, Fields::valueTypeName, m_valueTypeName);
cont = cont && self.dvValueField(visitor, Fields::extensionTypeName, m_extensionTypeName);
cont = cont && self.dvValueField(visitor, Fields::accessSemantics, int(m_accessSemantics));
return cont;
}
Export Export::fromString(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.size();
else
res.version = Version::fromString(exp.mid(spaceIdx + 1));
if (!res.version.isValid())
domParsingErrors()
.error(tr("Expected string literal to contain 'Package/Name major.minor' "
"or 'Name major.minor' not '%1'.")
.arg(exp))
.handle(h);
if (slashIdx != -1)
res.uri = exp.left(slashIdx).toString();
res.typeName = exp.mid(slashIdx + 1, spaceIdx - (slashIdx + 1)).toString();
return res;
}
bool AttributeInfo::iterateDirectSubpaths(const DomItem &self, DirectVisitor visitor) const
{
bool cont = true;
cont = cont && self.dvValueField(visitor, Fields::name, name);
cont = cont && self.dvValueField(visitor, Fields::access, int(access));
cont = cont && self.dvValueField(visitor, Fields::typeName, typeName);
cont = cont && self.dvValueField(visitor, Fields::isReadonly, isReadonly);
cont = cont && self.dvValueField(visitor, Fields::isList, isList);
cont = cont && self.dvWrapField(visitor, Fields::comments, comments);
cont = cont && self.dvWrapField(visitor, Fields::annotations, annotations);
return cont;
}
Path AttributeInfo::addAnnotation(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(const DomItem &self, DirectVisitor visitor) const
{
bool cont = CommentableDomElement::iterateDirectSubpaths(self, visitor);
cont = cont && self.dvValueField(visitor, Fields::name, name());
cont = cont && self.dvWrapField(visitor, Fields::values, m_values);
cont = cont && self.dvWrapField(visitor, Fields::annotations, m_annotations);
return cont;
}
void EnumDecl::updatePathFromOwner(Path newPath)
{
DomElement::updatePathFromOwner(newPath);
updatePathFromOwnerQList(m_annotations, newPath.field(Fields::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);
}
void EnumDecl::writeOut(const DomItem &self, OutWriter &ow) const
{
ow.writeRegion(EnumKeywordRegion)
.space()
.writeRegion(IdentifierRegion, name())
.space()
.writeRegion(LeftBraceRegion);
int iLevel = ow.increaseIndent(1);
const auto values = self.field(Fields::values).values();
for (const auto &value : values) {
ow.ensureNewline();
value.writeOut(ow);
}
ow.decreaseIndent(1, iLevel);
ow.ensureNewline().writeRegion(RightBraceRegion);
}
QList<Path> ImportScope::allSources(const DomItem &self) const
{
DomItem top = self.top();
DomItem env = top.environment();
Path selfPath = self.canonicalPath().field(Fields::allSources);
RefCacheEntry cached = (env ? RefCacheEntry::forPath(env, selfPath) : RefCacheEntry());
if (cached.cached == RefCacheEntry::Cached::All)
return cached.canonicalPaths;
QList<Path> res;
QSet<Path> knownPaths;
QList<Path> toDo(m_importSourcePaths.rbegin(), m_importSourcePaths.rend());
while (!toDo.isEmpty()) {
Path pNow = toDo.takeLast();
if (knownPaths.contains(pNow))
continue;
knownPaths.insert(pNow);
res.append(pNow);
DomItem sourceBase = top.path(pNow);
for (const DomItem &autoExp : sourceBase.field(Fields::autoExports).values()) {
if (const ModuleAutoExport *autoExpPtr = autoExp.as<ModuleAutoExport>()) {
Path newSource;
if (autoExpPtr->inheritVersion) {
Version v = autoExpPtr->import.version;
DomItem sourceVersion = sourceBase.field(Fields::version);
if (const Version *sourceVersionPtr = sourceVersion.as<Version>()) {
if (v.majorVersion < 0)
v.majorVersion = sourceVersionPtr->majorVersion;
if (v.minorVersion < 0)
v.minorVersion = sourceVersionPtr->minorVersion;
} else {
qWarning() << "autoExport with inherited version " << autoExp
<< " but missing version in source" << pNow;
}
Import toImport(autoExpPtr->import.uri, v);
newSource = toImport.importedPath();
} else {
newSource = autoExpPtr->import.importedPath();
}
if (newSource && !knownPaths.contains(newSource))
toDo.append(newSource);
} else {
qWarning() << "expected ModuleAutoExport not " << autoExp.internalKindStr()
<< "looking up autoExports of" << sourceBase;
Q_ASSERT(false);
}
}
}
if (env)
RefCacheEntry::addForPath(env, selfPath, RefCacheEntry { RefCacheEntry::Cached::All, res });
return res;
}
bool ImportScope::iterateDirectSubpaths(const DomItem &self, DirectVisitor visitor) const
{
bool cont = true;
cont = cont && self.dvReferencesField(visitor, Fields::importSources, m_importSourcePaths);
cont = cont && self.dvItemField(visitor, Fields::allSources, [this, &self]() -> DomItem {
return self.subListItem(List::fromQList<Path>(
self.pathFromOwner().field(Fields::allSources), allSources(self),
[](const DomItem &list, const PathEls::PathComponent &p, const Path &el) {
return list.subDataItem(p, el.toString());
}));
});
cont = cont && self.dvWrapField(visitor, Fields::qualifiedImports, m_subImports);
cont = cont && self.dvItemField(visitor, Fields::imported, [this, &self]() -> DomItem {
return self.subMapItem(Map(
self.pathFromOwner().field(Fields::imported),
[this, &self](const DomItem &map, QString key) {
return map.subListItem(List::fromQList<DomItem>(
map.pathFromOwner().key(key), importedItemsWithName(self, key),
[](const DomItem &, const PathEls::PathComponent &, const DomItem &el) {
return el;
}));
},
[this, &self](const DomItem &) { return this->importedNames(self); },
QLatin1String("List<Export>")));
});
return cont;
}
bool PropertyInfo::iterateDirectSubpaths(const DomItem &self, DirectVisitor visitor) const
{
bool cont = true;
cont = cont && self.dvValueField(visitor, Fields::propertyDefs, propertyDefs);
cont = cont && self.dvValueField(visitor, Fields::bindings, bindings);
return cont;
}
BindingValue::BindingValue() : kind(BindingValueKind::Empty) { }
BindingValue::BindingValue(const QmlObject &o) : kind(BindingValueKind::Object)
{
new (&object) QmlObject(o);
}
BindingValue::BindingValue(std::shared_ptr<ScriptExpression> o)
: kind(BindingValueKind::ScriptExpression)
{
new (&scriptExpression) std::shared_ptr<ScriptExpression>(o);
}
BindingValue::BindingValue(const QList<QmlObject> &l) : kind(BindingValueKind::Array)
{
new (&array) QList<QmlObject>(l);
}
BindingValue::~BindingValue()
{
clearValue();
}
BindingValue::BindingValue(const BindingValue &o) : kind(o.kind)
{
switch (o.kind) {
case BindingValueKind::Empty:
break;
case BindingValueKind::Object:
new (&object) QmlObject(o.object);
break;
case BindingValueKind::ScriptExpression:
new (&scriptExpression) std::shared_ptr<ScriptExpression>(o.scriptExpression);
break;
case BindingValueKind::Array:
new (&array) QList<QmlObject>(o.array);
}
}
BindingValue &BindingValue::operator=(const BindingValue &o)
{
clearValue();
kind = o.kind;
switch (o.kind) {
case BindingValueKind::Empty:
break;
case BindingValueKind::Object:
new (&object) QmlObject(o.object);
break;
case BindingValueKind::ScriptExpression:
new (&scriptExpression) std::shared_ptr<ScriptExpression>(o.scriptExpression);
break;
case BindingValueKind::Array:
new (&array) QList<QmlObject>(o.array);
}
return *this;
}
DomItem BindingValue::value(const DomItem &binding) const
{
switch (kind) {
case BindingValueKind::Empty:
break;
case BindingValueKind::Object:
return binding.copy(&object);
case BindingValueKind::ScriptExpression:
return binding.subOwnerItem(PathEls::Field(Fields::value), scriptExpression);
case BindingValueKind::Array:
return binding.subListItem(List::fromQListRef<QmlObject>(
binding.pathFromOwner().field(u"value"), array,
[](const DomItem &self, const PathEls::PathComponent &, const QmlObject &obj) {
return self.copy(&obj);
}));
}
return DomItem();
}
void BindingValue::updatePathFromOwner(Path newPath)
{
switch (kind) {
case BindingValueKind::Empty:
break;
case BindingValueKind::Object:
object.updatePathFromOwner(newPath);
break;
case BindingValueKind::ScriptExpression:
break;
case BindingValueKind::Array:
updatePathFromOwnerQList(array, newPath);
break;
}
}
void BindingValue::clearValue()
{
switch (kind) {
case BindingValueKind::Empty:
break;
case BindingValueKind::Object:
object.~QmlObject();
break;
case BindingValueKind::ScriptExpression:
scriptExpression.~shared_ptr();
break;
case BindingValueKind::Array:
array.~QList<QmlObject>();
break;
}
kind = BindingValueKind::Empty;
}
ScriptExpression::ScriptExpression(QStringView code, std::shared_ptr<QQmlJS::Engine> engine,
AST::Node *ast, std::shared_ptr<AstComments> comments,
ExpressionType expressionType, SourceLocation localOffset,
int derivedFrom, QStringView preCode, QStringView postCode)
: OwningItem(derivedFrom),
m_expressionType(expressionType),
m_code(code),
m_preCode(preCode),
m_postCode(postCode),
m_engine(engine),
m_ast(ast),
m_astComments(comments),
m_localOffset(localOffset)
{
if (m_expressionType == ExpressionType::BindingExpression)
if (AST::ExpressionStatement *exp = AST::cast<AST::ExpressionStatement *>(m_ast))
m_ast = exp->expression;
Q_ASSERT(m_astComments);
}
ScriptExpression::ScriptExpression(const ScriptExpression &e) : OwningItem(e)
{
QMutexLocker l(mutex());
m_expressionType = e.m_expressionType;
m_engine = e.m_engine;
m_ast = e.m_ast;
if (m_codeStr.isEmpty()) {
m_code = e.m_code;
} else {
m_codeStr = e.m_codeStr;
m_code = m_codeStr;
}
m_localOffset = e.m_localOffset;
m_astComments = e.m_astComments;
}
std::shared_ptr<ScriptExpression> ScriptExpression::copyWithUpdatedCode(const DomItem &self,
QString code) const
{
std::shared_ptr<ScriptExpression> copy = makeCopy(self);
DomItem container = self.containingObject();
QString preCodeStr = container.field(Fields::preCode).value().toString(m_preCode.toString());
QString postCodeStr = container.field(Fields::postCode).value().toString(m_postCode.toString());
copy->setCode(code, preCodeStr, postCodeStr);
return copy;
}
bool ScriptExpression::iterateDirectSubpaths(const DomItem &self, DirectVisitor visitor) const
{
bool cont = OwningItem::iterateDirectSubpaths(self, visitor);
cont = cont && self.dvValueField(visitor, Fields::code, code());
if (!preCode().isEmpty())
cont = cont
&& self.dvValueField(visitor, Fields::preCode, preCode(),
ConstantData::Options::MapIsMap);
if (!postCode().isEmpty())
cont = cont
&& self.dvValueField(visitor, Fields::postCode, postCode(),
ConstantData::Options::MapIsMap);
cont = cont
&& self.dvValueLazyField(
visitor, Fields::localOffset,
[this]() { return sourceLocationToQCborValue(localOffset()); },
ConstantData::Options::MapIsMap);
cont = cont && self.dvValueLazyField(visitor, Fields::astRelocatableDump, [this]() {
return astRelocatableDump();
});
cont = cont && self.dvValueField(visitor, Fields::expressionType, int(expressionType()));
Dom: Introduce scriptelements in the DOM representation Implement the Dom representation for some constructs, like: * Literals (string and numbers) * IdentifierExpressions (variableNames appearing in expressions) * VariableDeclarations and VariableDeclarationEntries * ForStatements * BlockStatements * IfStatements Introduce the namespace QQmlJS::Dom::ScriptElements that will contain the Dom representation of javascript statements and expressions. To incorporate the new DomItem wrappers in the Dom, extend the qqmldomastcreator to also visit the corresponding javascript AST parts. Like this, it can create the new Dom script elements using its stack: visit() pushes new elements on the stack while endVisit() collects the children of the current element from the stack (note: in the reverse order they were pushed on the stack). The implementation uses two stacks to avoid having to check every time if the top of the stack is a QmlStackElement or a ScriptStackElement, but logically both stacks work as one stack, containing first all QmlStackElements and then all ScriptStackElements. In case non-implemented script elements are encountered by QQmlDomAstCreator, it will disable the script expression construction and print a warning, instead of asserting and making a lot of unrelated tests fail. This mechanism can be removed once all script elements are implemented in the DOM. Wrap script element pointers into a ScriptElementVariant variant that can be used to contain any script element. Added a flag to enable script element construction during DOM construction. Task-number: QTBUG-92876 Change-Id: I24b070ca3ff6ce916036842280cf91a21d049f30 Reviewed-by: Fabian Kosmale <fabian.kosmale@qt.io>
2023-04-05 14:43:50 +00:00
if (m_element) {
cont = cont && self.dvItemField(visitor, Fields::scriptElement, [this, &self]() {
return self.subScriptElementWrapperItem(m_element);
});
}
return cont;
}
class FirstNodeVisitor : public VisitAll
{
public:
quint32 minStart = 0;
quint32 maxEnd = std::numeric_limits<quint32>::max();
AST::Node *firstNodeInRange = nullptr;
FirstNodeVisitor(quint32 minStart = 0, quint32 maxEnd = std::numeric_limits<quint32>::max())
: minStart(minStart), maxEnd(maxEnd)
{
}
bool preVisit(AST::Node *n) override
{
if (!VisitAll::uiKinds().contains(n->kind)) {
quint32 start = n->firstSourceLocation().begin();
quint32 end = n->lastSourceLocation().end();
if (!firstNodeInRange && minStart <= start && end <= maxEnd && start < end)
firstNodeInRange = n;
}
return !firstNodeInRange;
}
};
AST::Node *firstNodeInRange(AST::Node *n, quint32 minStart = 0, quint32 maxEnd = ~quint32(0))
{
FirstNodeVisitor visitor(minStart, maxEnd);
AST::Node::accept(n, &visitor);
return visitor.firstNodeInRange;
}
void ScriptExpression::setCode(QString code, QString preCode, QString postCode)
{
//TODO refactor and move parsing outside?
m_codeStr = code;
const bool qmlMode = (m_expressionType == ExpressionType::BindingExpression);
if (qmlMode && preCode.isEmpty()) {
preCode = Binding::preCodeForName(u"binding");
postCode = Binding::postCodeForName(u"binding");
}
if (!preCode.isEmpty() || !postCode.isEmpty())
m_codeStr = preCode + code + postCode;
m_code = QStringView(m_codeStr).mid(preCode.size(), code.size());
m_preCode = QStringView(m_codeStr).mid(0, preCode.size());
m_postCode = QStringView(m_codeStr).mid(preCode.size() + code.size(), postCode.size());
m_engine = nullptr;
m_ast = nullptr;
m_localOffset = SourceLocation();
if (!m_code.isEmpty()) {
IndentInfo preChange(m_preCode, 4);
m_localOffset.offset = m_preCode.size();
m_localOffset.length = m_code.size();
m_localOffset.startColumn = preChange.trailingString.size();
m_localOffset.startLine = preChange.nNewlines;
m_engine = std::make_shared<QQmlJS::Engine>();
m_astComments = std::make_shared<AstComments>(m_engine);
QQmlJS::Lexer lexer(m_engine.get());
lexer.setCode(m_codeStr, /*lineno = */ 1, /*qmlMode=*/true);
QQmlJS::Parser parser(m_engine.get());
if ((qmlMode && !parser.parse()) || (!qmlMode && !parser.parseScript()))
addErrorLocal(domParsingErrors().error(tr("Parsing of code failed")));
const auto messages = parser.diagnosticMessages();
for (const DiagnosticMessage &msg : messages) {
ErrorMessage err = domParsingErrors().errorMessage(msg);
err.location.offset -= m_localOffset.offset;
err.location.startLine -= m_localOffset.startLine;
if (err.location.startLine == 1)
err.location.startColumn -= m_localOffset.startColumn;
addErrorLocal(std::move(err));
}
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.size(),
m_preCode.size() + m_code.size());
if (auto *sList = AST::cast<AST::FormalParameterList *>(m_ast)) {
m_ast = sList->element;
}
if (m_expressionType != ExpressionType::FunctionBody) {
if (AST::StatementList *sList = AST::cast<AST::StatementList *>(m_ast)) {
if (!sList->next)
m_ast = sList->statement;
}
}
if (m_expressionType == ExpressionType::BindingExpression)
if (AST::ExpressionStatement *exp = AST::cast<AST::ExpressionStatement *>(m_ast))
m_ast = exp->expression;
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);
});
}
void ScriptExpression::writeOut(const DomItem &self, OutWriter &lw) const
{
OutWriter *ow = &lw;
std::optional<PendingSourceLocationId> codeLoc;
if (lw.lineWriter.options().updateOptions & LineWriterOptions::Update::Expressions)
codeLoc = lw.lineWriter.startSourceLocation([this, self, ow](SourceLocation myLoc) mutable {
QStringView reformattedCode =
QStringView(ow->writtenStr).mid(myLoc.offset, myLoc.length);
if (reformattedCode != code()) {
//If some reformatting of the expression took place,
//it will be saved as an intermediate step.
//then it will be used to assemble the final reformatted file
//in the OutWriter::updatedFile
//Interestingly enough, this copyWithUpdatedCode will
//instantiate Engine and Parser and will parse "reformattedCode"
//because it calls ScriptExpression::setCode function
std::shared_ptr<ScriptExpression> copy =
copyWithUpdatedCode(self, reformattedCode.toString());
ow->addReformattedScriptExpression(self.canonicalPath(), copy);
}
});
reformatAst(
lw, m_astComments,
[this](SourceLocation astL) {
SourceLocation l = this->locationToLocal(astL); // use engine->code() instead?
return this->code().mid(l.offset, l.length);
},
ast());
if (codeLoc)
lw.lineWriter.endSourceLocation(*codeLoc);
}
SourceLocation ScriptExpression::globalLocation(const DomItem &self) const
{
if (const FileLocations::Tree tree = FileLocations::treeOf(self)) {
return FileLocations::region(tree, MainRegion);
}
return SourceLocation();
}
bool PropertyDefinition::isParametricType() const
{
return typeName.contains(QChar(u'<'));
}
void PropertyDefinition::writeOut(const DomItem &, OutWriter &lw) const
{
lw.ensureNewline();
if (isDefaultMember)
lw.writeRegion(DefaultKeywordRegion).space();
if (isRequired)
lw.writeRegion(RequiredKeywordRegion).space();
if (isReadonly)
lw.writeRegion(ReadonlyKeywordRegion).space();
if (!typeName.isEmpty()) {
lw.writeRegion(PropertyKeywordRegion).space();
lw.writeRegion(TypeIdentifierRegion, typeName).space();
}
lw.writeRegion(IdentifierRegion, name);
}
bool MethodInfo::iterateDirectSubpaths(const DomItem &self, DirectVisitor visitor) const
{
bool cont = AttributeInfo::iterateDirectSubpaths(self, visitor);
cont = cont && self.dvWrapField(visitor, Fields::parameters, parameters);
cont = cont && self.dvValueField(visitor, Fields::methodType, int(methodType));
if (!typeName.isEmpty())
cont = cont && self.dvReferenceField(visitor, Fields::type, typePath(self));
if (methodType == MethodType::Method) {
cont = cont && self.dvValueField(visitor, Fields::preCode, preCode(self));
cont = cont && self.dvValueField(visitor, Fields::postCode, postCode(self));
cont = cont && self.dvValueField(visitor, Fields::isConstructor, isConstructor);
}
QmlDom: support function parameters and co Implement function parameters for function definitions in the Dom, as otherwise the unused scriptelements in the stack will make the following script element construction fail. Also implement all the dom elements required by function parameters. Instead of filling in only the script element for the defaultValues, (and failing when encountering more complex expressions), add a ScriptExpression that can model more complex expressions in the argument (including default value, deconstruction, type annotations, etc.) in the Dom::MethodParameter class and leave the pre-existing defaultValue as is for now. Requirements for successful function parameter construction: * Add support for JS arrays and JS objects literals in the Dom. These ones are required to model parameter deconstruction, e.g. to pick certain list elements or object properties from the argument object/array * Fix the iteration order for PatternElementLists and PatternPropertyLists, as both are required for the JS arrays and objects * Add all kind of property names (used in JS object literals and JS object deconstruction) as literals in qqmldomastcreator. * JS array and object deconstruction happened to reveal a bug for VariableDeclarations, fix it (because you can use deconstruction in variable declarations) + add tests for that. * Support Type annotations in the Dom: this means that type annotations for methods are also created and also needs to be collected. Add a field returnType in Dom::MethodInfo for this. * Make sure that all QQmlJSScope's in the Dom are wrapped in optionals (because they are null when the semantic analysis option is not passed) and adapt the getters to it. Task-number: QTBUG-92876 Change-Id: I81df66989e833c9acd75f854b49dcc15b0729e99 Reviewed-by: Fabian Kosmale <fabian.kosmale@qt.io>
2023-05-30 14:04:01 +00:00
if (returnType)
cont = cont && self.dvItemField(visitor, Fields::returnType, [this, &self]() {
return self.subOwnerItem(PathEls::Field(Fields::returnType), returnType);
});
if (body)
cont = cont && self.dvItemField(visitor, Fields::body, [this, &self]() {
return self.subOwnerItem(PathEls::Field(Fields::body), body);
});
return cont;
}
QString MethodInfo::preCode(const DomItem &self) const
{
QString res;
LineWriter lw([&res](QStringView s) { res.append(s); }, QLatin1String("*preCode*"));
OutWriter ow(lw);
ow.indentNextlines = true;
ow.skipComments = true;
MockObject standinObj(self.pathFromOwner());
DomItem standin = self.copy(&standinObj);
ow.itemStart(standin);
ow.writeRegion(FunctionKeywordRegion).space().writeRegion(IdentifierRegion, name);
bool first = true;
ow.writeRegion(LeftParenthesisRegion);
for (const MethodParameter &mp : parameters) {
if (first)
first = false;
else
ow.write(u", ");
ow.write(mp.value->code());
}
ow.writeRegion(RightParenthesisRegion);
ow.ensureSpace().writeRegion(LeftBraceRegion);
ow.itemEnd(standin);
ow.eof();
return res;
}
QString MethodInfo::postCode(const DomItem &) const
{
return QLatin1String("\n}\n");
}
void MethodInfo::writeOut(const DomItem &self, OutWriter &ow) const
{
switch (methodType) {
case MethodType::Signal: {
if (body)
qCWarning(domLog) << "signal should not have a body in" << self.canonicalPath();
ow.writeRegion(SignalKeywordRegion).space().writeRegion(IdentifierRegion, name);
if (parameters.isEmpty())
return;
bool first = true;
ow.writeRegion(LeftParenthesisRegion);
int baseIndent = ow.increaseIndent();
for (const DomItem &arg : self.field(Fields::parameters).values()) {
if (first)
first = false;
else
ow.write(u", ");
if (const MethodParameter *argPtr = arg.as<MethodParameter>())
argPtr->writeOutSignal(arg, ow);
else
qCWarning(domLog) << "failed to cast to MethodParameter";
}
ow.writeRegion(RightParenthesisRegion);
ow.decreaseIndent(1, baseIndent);
return;
} break;
case MethodType::Method: {
ow.writeRegion(FunctionKeywordRegion).space().writeRegion(IdentifierRegion, name);
bool first = true;
ow.writeRegion(LeftParenthesisRegion);
for (const DomItem &arg : self.field(Fields::parameters).values()) {
if (first)
first = false;
else
ow.write(u", ");
arg.writeOut(ow);
}
ow.writeRegion(RightParenthesisRegion);
if (!typeName.isEmpty()) {
ow.writeRegion(ColonTokenRegion);
ow.space();
ow.writeRegion(TypeIdentifierRegion, typeName);
}
ow.ensureSpace().writeRegion(LeftBraceRegion);
int baseIndent = ow.increaseIndent();
if (DomItem b = self.field(Fields::body)) {
ow.ensureNewline();
b.writeOut(ow);
}
ow.decreaseIndent(1, baseIndent);
ow.ensureNewline().writeRegion(RightBraceRegion);
} break;
}
}
bool MethodParameter::iterateDirectSubpaths(const DomItem &self, DirectVisitor visitor) const
{
bool cont = true;
cont = cont && self.dvValueField(visitor, Fields::name, name);
if (!typeName.isEmpty()) {
cont = cont
&& self.dvReferenceField(visitor, Fields::type, Paths::lookupTypePath(typeName));
cont = cont && self.dvValueField(visitor, Fields::typeName, typeName);
}
cont = cont && self.dvValueField(visitor, Fields::isPointer, isPointer);
cont = cont && self.dvValueField(visitor, Fields::isReadonly, isReadonly);
cont = cont && self.dvValueField(visitor, Fields::isList, isList);
cont = cont && self.dvWrapField(visitor, Fields::defaultValue, defaultValue);
QmlDom: support function parameters and co Implement function parameters for function definitions in the Dom, as otherwise the unused scriptelements in the stack will make the following script element construction fail. Also implement all the dom elements required by function parameters. Instead of filling in only the script element for the defaultValues, (and failing when encountering more complex expressions), add a ScriptExpression that can model more complex expressions in the argument (including default value, deconstruction, type annotations, etc.) in the Dom::MethodParameter class and leave the pre-existing defaultValue as is for now. Requirements for successful function parameter construction: * Add support for JS arrays and JS objects literals in the Dom. These ones are required to model parameter deconstruction, e.g. to pick certain list elements or object properties from the argument object/array * Fix the iteration order for PatternElementLists and PatternPropertyLists, as both are required for the JS arrays and objects * Add all kind of property names (used in JS object literals and JS object deconstruction) as literals in qqmldomastcreator. * JS array and object deconstruction happened to reveal a bug for VariableDeclarations, fix it (because you can use deconstruction in variable declarations) + add tests for that. * Support Type annotations in the Dom: this means that type annotations for methods are also created and also needs to be collected. Add a field returnType in Dom::MethodInfo for this. * Make sure that all QQmlJSScope's in the Dom are wrapped in optionals (because they are null when the semantic analysis option is not passed) and adapt the getters to it. Task-number: QTBUG-92876 Change-Id: I81df66989e833c9acd75f854b49dcc15b0729e99 Reviewed-by: Fabian Kosmale <fabian.kosmale@qt.io>
2023-05-30 14:04:01 +00:00
cont = cont && self.dvWrapField(visitor, Fields::value, value);
cont = cont && self.dvValueField(visitor, Fields::preCode, u"function f("_s);
cont = cont && self.dvValueField(visitor, Fields::postCode, u") {}"_s);
if (!annotations.isEmpty())
cont = cont && self.dvWrapField(visitor, Fields::annotations, annotations);
cont = cont && self.dvWrapField(visitor, Fields::comments, comments);
return cont;
}
void MethodParameter::writeOut(const DomItem &self, OutWriter &ow) const
{
if (!name.isEmpty()) {
if (isRestElement)
ow.writeRegion(EllipsisTokenRegion);
ow.writeRegion(IdentifierRegion, name);
if (!typeName.isEmpty())
ow.writeRegion(ColonTokenRegion).space().writeRegion(TypeIdentifierRegion, typeName);
if (defaultValue) {
ow.space().writeRegion(EqualTokenRegion).space();
self.subOwnerItem(PathEls::Field(Fields::defaultValue), defaultValue).writeOut(ow);
}
} else {
if (value) {
self.subOwnerItem(PathEls::Field(Fields::value), value).writeOut(ow);
}
}
}
void MethodParameter::writeOutSignal(const DomItem &self, OutWriter &ow) const
{
self.writeOutPre(ow);
if (!typeName.isEmpty())
ow.writeRegion(TypeIdentifierRegion, typeName).space();
ow.writeRegion(IdentifierRegion, name);
self.writeOutPost(ow);
}
void Pragma::writeOut(const DomItem &, OutWriter &ow) const
{
ow.ensureNewline();
ow.writeRegion(PragmaKeywordRegion).space().writeRegion(IdentifierRegion, name);
bool isFirst = true;
for (const auto &value : values) {
if (isFirst) {
isFirst = false;
ow.writeRegion(ColonTokenRegion).space();
ow.writeRegion(PragmaValuesRegion, value);
continue;
}
ow.writeRegion(CommaTokenRegion).space();
ow.writeRegion(PragmaValuesRegion, value);
}
ow.ensureNewline();
}
bool EnumItem::iterateDirectSubpaths(const DomItem &self, DirectVisitor visitor) const
{
bool cont = true;
cont = cont && self.dvValueField(visitor, Fields::name, name());
cont = cont && self.dvValueField(visitor, Fields::value, value());
cont = cont && self.dvWrapField(visitor, Fields::comments, m_comments);
return cont;
}
void EnumItem::writeOut(const DomItem &self, OutWriter &ow) const
{
ow.ensureNewline();
ow.writeRegion(IdentifierRegion, name());
bool hasDefaultValue = false;
index_type myIndex = self.pathFromOwner().last().headIndex();
if (myIndex == 0)
hasDefaultValue = value() == 0;
else if (myIndex > 0)
hasDefaultValue = value()
== self.container()
.index(myIndex - 1)
.field(Fields::value)
.value()
.toDouble(value())
+ 1;
if (!hasDefaultValue) {
QString v = QString::number(value(), 'f', 0);
if (abs(value() - v.toDouble()) > 1.e-10)
v = QString::number(value());
ow.space().writeRegion(EqualTokenRegion).space().writeRegion(PragmaValuesRegion, v);
}
if (myIndex >= 0 && self.container().indexes() != myIndex + 1)
ow.writeRegion(CommaTokenRegion);
}
QmlUri QmlUri::fromString(const QString &str)
{
if (str.startsWith(u'"'))
return fromDirectoryString(str.mid(1, str.size() - 2)
.replace(u"\\\""_s, u"\""_s)
.replace(u"\\\\"_s, u"\\"_s));
else
return fromUriString(str);
}
QmlUri QmlUri::fromUriString(const QString &str)
{
QRegularExpression moduleUriRe(QLatin1String(R"(\A\w+(?:\.\w+)*\Z)"));
return QmlUri((moduleUriRe.match(str).hasMatch() ? Kind::ModuleUri : Kind::Invalid), str);
}
QmlUri QmlUri::fromDirectoryString(const QString &str)
{
QUrl url(str);
if (url.isValid() && url.scheme().size() > 1)
return QmlUri(url);
if (!str.isEmpty()) {
QFileInfo path(str);
return QmlUri((path.isRelative() ? Kind::RelativePath : Kind::AbsolutePath), str);
}
return {};
}
bool QmlUri::isValid() const
{
return m_kind != Kind::Invalid;
}
bool QmlUri::isDirectory() const
{
switch (m_kind) {
case Kind::Invalid:
case Kind::ModuleUri:
break;
case Kind::DirectoryUrl:
case Kind::RelativePath:
case Kind::AbsolutePath:
return true;
}
return false;
}
bool QmlUri::isModule() const
{
return m_kind == Kind::ModuleUri;
}
QString QmlUri::moduleUri() const
{
if (m_kind == Kind::ModuleUri)
return std::get<QString>(m_value);
return QString();
}
QString QmlUri::localPath() const
{
switch (m_kind) {
case Kind::Invalid:
case Kind::ModuleUri:
break;
case Kind::DirectoryUrl: {
const QUrl &url = std::get<QUrl>(m_value);
if (url.scheme().compare(u"file", Qt::CaseInsensitive) == 0)
return url.path();
break;
}
case Kind::RelativePath:
case Kind::AbsolutePath:
return std::get<QString>(m_value);
}
return QString();
}
QString QmlUri::absoluteLocalPath(const QString &basePath) const
{
switch (m_kind) {
case Kind::Invalid:
case Kind::ModuleUri:
break;
case Kind::DirectoryUrl: {
const QUrl &url = std::get<QUrl>(m_value);
if (url.scheme().compare(u"file", Qt::CaseInsensitive) == 0)
return url.path();
break;
}
case Kind::RelativePath: {
if (!basePath.isEmpty())
return QDir(basePath).filePath(std::get<QString>(m_value));
break;
}
case Kind::AbsolutePath:
return std::get<QString>(m_value);
}
return QString();
}
QUrl QmlUri::directoryUrl() const
{
if (m_kind == Kind::DirectoryUrl)
return std::get<QUrl>(m_value);
return QUrl {};
}
QString QmlUri::directoryString() const
{
switch (m_kind) {
case Kind::Invalid:
case Kind::ModuleUri:
break;
case Kind::DirectoryUrl:
return std::get<QUrl>(m_value).toString(); // set formatting? options?
case Kind::RelativePath:
case Kind::AbsolutePath:
return std::get<QString>(m_value);
}
return QString();
}
QString QmlUri::toString() const
{
switch (m_kind) {
case Kind::Invalid:
break;
case Kind::ModuleUri:
return std::get<QString>(m_value);
case Kind::DirectoryUrl:
case Kind::RelativePath:
case Kind::AbsolutePath:
return u"\""_s + directoryString().replace(u'\\', u"\\\\"_s).replace(u'"', u"\\\""_s)
+ u"\""_s;
}
return QString();
}
QmlUri::Kind QmlUri::kind() const
{
return m_kind;
}
Dom: Introduce scriptelements in the DOM representation Implement the Dom representation for some constructs, like: * Literals (string and numbers) * IdentifierExpressions (variableNames appearing in expressions) * VariableDeclarations and VariableDeclarationEntries * ForStatements * BlockStatements * IfStatements Introduce the namespace QQmlJS::Dom::ScriptElements that will contain the Dom representation of javascript statements and expressions. To incorporate the new DomItem wrappers in the Dom, extend the qqmldomastcreator to also visit the corresponding javascript AST parts. Like this, it can create the new Dom script elements using its stack: visit() pushes new elements on the stack while endVisit() collects the children of the current element from the stack (note: in the reverse order they were pushed on the stack). The implementation uses two stacks to avoid having to check every time if the top of the stack is a QmlStackElement or a ScriptStackElement, but logically both stacks work as one stack, containing first all QmlStackElements and then all ScriptStackElements. In case non-implemented script elements are encountered by QQmlDomAstCreator, it will disable the script expression construction and print a warning, instead of asserting and making a lot of unrelated tests fail. This mechanism can be removed once all script elements are implemented in the DOM. Wrap script element pointers into a ScriptElementVariant variant that can be used to contain any script element. Added a flag to enable script element construction during DOM construction. Task-number: QTBUG-92876 Change-Id: I24b070ca3ff6ce916036842280cf91a21d049f30 Reviewed-by: Fabian Kosmale <fabian.kosmale@qt.io>
2023-04-05 14:43:50 +00:00
void ScriptExpression::setScriptElement(const ScriptElementVariant &p)
{
m_element = p;
}
} // end namespace Dom
} // end namespace QQmlJS
QT_END_NAMESPACE
#include "moc_qqmldomelements_p.cpp"