diff options
Diffstat (limited to 'src/qmldom/qqmldomastcreator.cpp')
-rw-r--r-- | src/qmldom/qqmldomastcreator.cpp | 3052 |
1 files changed, 3052 insertions, 0 deletions
diff --git a/src/qmldom/qqmldomastcreator.cpp b/src/qmldom/qqmldomastcreator.cpp new file mode 100644 index 0000000000..67e3acff1c --- /dev/null +++ b/src/qmldom/qqmldomastcreator.cpp @@ -0,0 +1,3052 @@ +// Copyright (C) 2021 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + +#include "qqmldomastcreator_p.h" +#include "qqmldomconstants_p.h" +#include "qqmldomelements_p.h" +#include "qqmldomitem_p.h" +#include "qqmldompath_p.h" +#include "qqmldomscriptelements_p.h" +#include "qqmldomtop_p.h" +#include "qqmldomerrormessage_p.h" +#include "qqmldomastdumper_p.h" +#include "qqmldomattachedinfo_p.h" +#include "qqmldomastcreator_p.h" +#include "qqmldom_utils_p.h" + +#include <QtQml/private/qqmljsast_p.h> +#include <QtQmlCompiler/private/qqmljsutils_p.h> + +#include <QtCore/QDir> +#include <QtCore/QFileInfo> +#include <QtCore/QScopeGuard> +#include <QtCore/QLoggingCategory> + +#include <memory> +#include <optional> +#include <type_traits> +#include <variant> +#include <vector> + +static Q_LOGGING_CATEGORY(creatorLog, "qt.qmldom.astcreator", QtWarningMsg); + +/* + Avoid crashing on files with JS-elements that are not implemented yet. + Might be removed (definition + usages) once all script elements are implemented. +*/ +#define Q_SCRIPTELEMENT_DISABLE() \ + do { \ + qDebug() << "Could not construct the JS DOM at" << __FILE__ << ":" << __LINE__ \ + << ", skipping JS elements..."; \ + disableScriptElements(); \ + } while (false) + +#define Q_SCRIPTELEMENT_EXIT_IF(check) \ + do { \ + if (m_enableScriptExpressions && (check)) { \ + Q_SCRIPTELEMENT_DISABLE(); \ + return; \ + } \ + } while (false) + +QT_BEGIN_NAMESPACE +namespace QQmlJS { +namespace Dom { + +using namespace AST; + +template<typename K, typename V> +V *valueFromMultimap(QMultiMap<K, V> &mmap, const K &key, index_type idx) +{ + if (idx < 0) + return nullptr; + auto it = mmap.find(key); + auto end = mmap.end(); + if (it == end) + return nullptr; + auto it2 = it; + index_type nEl = 0; + while (it2 != end && it2.key() == key) { + ++it2; + ++nEl; + } + if (nEl <= idx) + return nullptr; + for (index_type i = idx + 1; i < nEl; ++i) + ++it; + return &(*it); +} + +static ErrorGroups astParseErrors() +{ + static ErrorGroups errs = { { NewErrorGroup("Dom"), NewErrorGroup("QmlFile"), + NewErrorGroup("Parsing") } }; + return errs; +} + +static QString toString(const UiQualifiedId *qualifiedId, QChar delimiter = QLatin1Char('.')) +{ + QString result; + + for (const UiQualifiedId *iter = qualifiedId; iter; iter = iter->next) { + if (iter != qualifiedId) + result += delimiter; + + result += iter->name; + } + + return result; +} + +static QString typeToString(AST::Type *t) +{ + Q_ASSERT(t); + QString res = toString(t->typeId); + + if (UiQualifiedId *arg = t->typeArgument) + res += u'<' + toString(arg) + u'>'; + + return res; +} + +SourceLocation combineLocations(SourceLocation s1, SourceLocation s2) +{ + return combine(s1, s2); +} + +SourceLocation combineLocations(Node *n) +{ + return combineLocations(n->firstSourceLocation(), n->lastSourceLocation()); +} + +static ScriptElementVariant wrapIntoFieldMemberExpression(const ScriptElementVariant &left, + const SourceLocation &dotToken, + const ScriptElementVariant &right) +{ + SourceLocation s1, s2; + left.visitConst([&s1](auto &&el) { s1 = el->mainRegionLocation(); }); + right.visitConst([&s2](auto &&el) { s2 = el->mainRegionLocation(); }); + + auto result = std::make_shared<ScriptElements::BinaryExpression>(s1, s2); + result->addLocation(OperatorTokenRegion, dotToken); + result->setOp(ScriptElements::BinaryExpression::FieldMemberAccess); + result->setLeft(left); + result->setRight(right); + return ScriptElementVariant::fromElement(result); +}; + +/*! + \internal + Creates a FieldMemberExpression if the qualified id has dots. +*/ +static ScriptElementVariant +fieldMemberExpressionForQualifiedId(const AST::UiQualifiedId *qualifiedId) +{ + ScriptElementVariant bindable; + bool first = true; + for (auto exp = qualifiedId; exp; exp = exp->next) { + const SourceLocation identifierLoc = exp->identifierToken; + auto id = std::make_shared<ScriptElements::IdentifierExpression>(identifierLoc); + id->setName(exp->name); + if (first) { + first = false; + bindable = ScriptElementVariant::fromElement(id); + continue; + } + bindable = wrapIntoFieldMemberExpression(bindable, exp->dotToken, + ScriptElementVariant::fromElement(id)); + } + + return bindable; +} + +QQmlDomAstCreator::QmlStackElement &QQmlDomAstCreator::currentQmlObjectOrComponentEl(int idx) +{ + Q_ASSERT_X(idx < nodeStack.size() && idx >= 0, "currentQmlObjectOrComponentEl", + "Stack does not contain enough elements!"); + int i = nodeStack.size() - idx; + while (i-- > 0) { + DomType k = nodeStack.at(i).item.kind; + if (k == DomType::QmlObject || k == DomType::QmlComponent) + return nodeStack[i]; + } + Q_ASSERT_X(false, "currentQmlObjectEl", "No QmlObject or component in stack"); + return nodeStack.last(); +} + +QQmlDomAstCreator::QmlStackElement &QQmlDomAstCreator::currentNodeEl(int i) +{ + Q_ASSERT_X(i < nodeStack.size() && i >= 0, "currentNode", "Stack does not contain element!"); + return nodeStack[nodeStack.size() - i - 1]; +} + +QQmlDomAstCreator::ScriptStackElement &QQmlDomAstCreator::currentScriptNodeEl(int i) +{ + Q_ASSERT_X(i < scriptNodeStack.size() && i >= 0, "currentNode", + "Stack does not contain element!"); + return scriptNodeStack[scriptNodeStack.size() - i - 1]; +} + +QQmlDomAstCreator::DomValue &QQmlDomAstCreator::currentNode(int i) +{ + Q_ASSERT_X(i < nodeStack.size() && i >= 0, "currentNode", + "Stack does not contain element!"); + return nodeStack[nodeStack.size() - i - 1].item; +} + +void QQmlDomAstCreator::removeCurrentNode(std::optional<DomType> expectedType) +{ + Q_ASSERT_X(!nodeStack.isEmpty(), className, "popCurrentNode() without any node"); + if (expectedType) + Q_ASSERT(nodeStack.last().item.kind == *expectedType); + nodeStack.removeLast(); +} + +void QQmlDomAstCreator::removeCurrentScriptNode(std::optional<DomType> expectedType) +{ + Q_SCRIPTELEMENT_EXIT_IF(scriptNodeStack.isEmpty()); + Q_ASSERT_X(!scriptNodeStack.isEmpty(), className, + "popCurrentScriptNode() without any node"); + if (expectedType) + Q_ASSERT(scriptNodeStack.last().kind == *expectedType); + scriptNodeStack.removeLast(); +} + +/*! + \internal + Prepares a script element DOM representation such that it can be used inside a QML DOM element. + This recursively sets the pathFromOwner and creates the FileLocations::Tree for all children of + element. + + Beware that pathFromOwner is appended to ownerFileLocations when creating the FileLocations! + + Make sure to add, for each of its use, a test in tst_qmldomitem:finalizeScriptExpressions, as + using a wrong pathFromOwner and/or a wrong base might lead to bugs hard to debug and spurious + crashes. + */ +const ScriptElementVariant & +QQmlDomAstCreator::finalizeScriptExpression(const ScriptElementVariant &element, const Path &pathFromOwner, + const FileLocations::Tree &ownerFileLocations) +{ + auto e = element.base(); + Q_ASSERT(e); + + qCDebug(creatorLog) << "Finalizing script expression with path:" + << ownerFileLocations->canonicalPathForTesting().append( + pathFromOwner.toString()); + e->updatePathFromOwner(pathFromOwner); + e->createFileLocations(ownerFileLocations); + return element; +} + +FileLocations::Tree QQmlDomAstCreator::createMap(const FileLocations::Tree &base, const Path &p, AST::Node *n) +{ + FileLocations::Tree res = FileLocations::ensure(base, p, AttachedInfo::PathType::Relative); + if (n) + FileLocations::addRegion(res, MainRegion, combineLocations(n)); + return res; +} + +FileLocations::Tree QQmlDomAstCreator::createMap(DomType k, const Path &p, AST::Node *n) +{ + Path relative; + FileLocations::Tree base; + switch (k) { + case DomType::QmlObject: + switch (currentNode().kind) { + case DomType::QmlObject: + case DomType::QmlComponent: + case DomType::PropertyDefinition: + case DomType::Binding: + case DomType::Id: + case DomType::MethodInfo: + break; + default: + qCWarning(domLog) << "unexpected type" << domTypeToString(currentNode().kind); + Q_UNREACHABLE(); + } + base = currentNodeEl().fileLocations; + if (p.length() > 2) { + Path p2 = p[p.length() - 2]; + if (p2.headKind() == Path::Kind::Field + && (p2.checkHeadName(Fields::children) || p2.checkHeadName(Fields::objects) + || p2.checkHeadName(Fields::value) || p2.checkHeadName(Fields::annotations) + || p2.checkHeadName(Fields::children))) + relative = p.mid(p.length() - 2, 2); + else if (p.last().checkHeadName(Fields::value) + && p.last().headKind() == Path::Kind::Field) + relative = p.last(); + else { + qCWarning(domLog) << "unexpected path to QmlObject in createMap" << p; + Q_UNREACHABLE(); + } + } else { + qCWarning(domLog) << "unexpected path to QmlObject in createMap" << p; + Q_UNREACHABLE(); + } + break; + case DomType::EnumItem: + relative = p; + base = currentNodeEl().fileLocations; + break; + case DomType::QmlComponent: + case DomType::Pragma: + case DomType::Import: + case DomType::Id: + case DomType::EnumDecl: + relative = p; + base = rootMap; + break; + case DomType::Binding: + case DomType::PropertyDefinition: + case DomType::MethodInfo: + base = currentEl<QmlObject>().fileLocations; + if (p.length() > 3) + relative = p.mid(p.length() - 3, 3); + else + relative = p; + break; + + default: + qCWarning(domLog) << "Unexpected type in createMap:" << domTypeToString(k); + Q_UNREACHABLE(); + break; + } + return createMap(base, relative, n); +} + +QQmlDomAstCreator::QQmlDomAstCreator(const MutableDomItem &qmlFile) + : qmlFile(qmlFile), + qmlFilePtr(qmlFile.ownerAs<QmlFile>()), + rootMap(qmlFilePtr->fileLocationsTree()) +{ +} + +bool QQmlDomAstCreator::visit(UiProgram *program) +{ + QFileInfo fInfo(qmlFile.canonicalFilePath()); + QString componentName = fInfo.baseName(); + QmlComponent *cPtr; + Path p = qmlFilePtr->addComponent(QmlComponent(componentName), AddOption::KeepExisting, + &cPtr); + MutableDomItem newC(qmlFile.item(), p); + Q_ASSERT_X(newC.item(), className, "could not recover component added with addComponent"); + // QmlFile region == Component region == program span + // we hide the component span because the component s written after the imports + FileLocations::addRegion(rootMap, MainRegion, combineLocations(program)); + pushEl(p, *cPtr, program); + + auto envPtr = qmlFile.environment().ownerAs<DomEnvironment>(); + const bool loadDependencies = + !envPtr->options().testFlag(DomEnvironment::Option::NoDependencies); + // add implicit directory import and load them in the Dom + if (!fInfo.canonicalPath().isEmpty()) { + Import selfDirImport(QmlUri::fromDirectoryString(fInfo.canonicalPath())); + selfDirImport.implicit = true; + qmlFilePtr->addImport(selfDirImport); + + if (loadDependencies) { + const QString currentFileDir = + QFileInfo(qmlFile.canonicalFilePath()).dir().canonicalPath(); + envPtr->loadFile(FileToLoad::fromFileSystem( + envPtr, selfDirImport.uri.absoluteLocalPath(currentFileDir)), + DomItem::Callback(), DomType::QmlDirectory); + } + } + // add implicit imports from the environment (QML, QtQml for example) and load them in the Dom + for (Import i : qmlFile.environment().ownerAs<DomEnvironment>()->implicitImports()) { + i.implicit = true; + qmlFilePtr->addImport(i); + + if (loadDependencies) + envPtr->loadModuleDependency(i.uri.moduleUri(), i.version, DomItem::Callback()); + } + if (m_loadFileLazily && loadDependencies) { + envPtr->loadPendingDependencies(); + envPtr->commitToBase(qmlFile.environment().item()); + } + + return true; +} + +void QQmlDomAstCreator::endVisit(AST::UiProgram *) +{ + MutableDomItem newC = qmlFile.path(currentNodeEl().path); + QmlComponent &comp = current<QmlComponent>(); + for (const Pragma &p : qmlFilePtr->pragmas()) { + if (p.name.compare(u"singleton", Qt::CaseInsensitive) == 0) { + comp.setIsSingleton(true); + comp.setIsCreatable(false); // correct? + } + } + *newC.mutableAs<QmlComponent>() = comp; + removeCurrentNode(DomType::QmlComponent); + Q_ASSERT_X(nodeStack.isEmpty(), className, "ui program did not finish node stack"); +} + +bool QQmlDomAstCreator::visit(UiPragma *el) +{ + QStringList valueList; + for (auto t = el->values; t; t = t->next) + valueList << t->value.toString(); + + auto fileLocation = createMap( + DomType::Pragma, qmlFilePtr->addPragma(Pragma(el->name.toString(), valueList)), el); + FileLocations::addRegion(fileLocation, PragmaKeywordRegion, el->pragmaToken); + FileLocations::addRegion(fileLocation, IdentifierRegion, el->pragmaIdToken); + if (el->colonToken.isValid()) { + FileLocations::addRegion(fileLocation, ColonTokenRegion, el->colonToken); + } + int i = 0; + for (auto t = el->values; t; t = t->next) { + auto subMap = createMap(fileLocation, Path().field(Fields::values).index(i), t); + FileLocations::addRegion(subMap, PragmaValuesRegion, t->location); + ++i; + } + + return true; +} + +bool QQmlDomAstCreator::visit(UiImport *el) +{ + Version v(Version::Latest, Version::Latest); + if (el->version && el->version->version.hasMajorVersion()) + v.majorVersion = el->version->version.majorVersion(); + if (el->version && el->version->version.hasMinorVersion()) + v.minorVersion = el->version->version.minorVersion(); + + auto envPtr = qmlFile.environment().ownerAs<DomEnvironment>(); + const bool loadDependencies = + !envPtr->options().testFlag(DomEnvironment::Option::NoDependencies); + FileLocations::Tree fileLocation; + if (el->importUri != nullptr) { + const Import import = + Import::fromUriString(toString(el->importUri), v, el->importId.toString()); + fileLocation = createMap(DomType::Import, qmlFilePtr->addImport(import), el); + + if (loadDependencies) { + envPtr->loadModuleDependency(import.uri.moduleUri(), import.version, + DomItem::Callback()); + } + FileLocations::addRegion(fileLocation, ImportUriRegion, combineLocations(el->importUri)); + } else { + const Import import = + Import::fromFileString(el->fileName.toString(), el->importId.toString()); + fileLocation = createMap(DomType::Import, qmlFilePtr->addImport(import), el); + + if (loadDependencies) { + const QString currentFileDir = + QFileInfo(qmlFile.canonicalFilePath()).dir().canonicalPath(); + envPtr->loadFile(FileToLoad::fromFileSystem( + envPtr, import.uri.absoluteLocalPath(currentFileDir)), + DomItem::Callback(), DomType::QmlDirectory); + } + FileLocations::addRegion(fileLocation, ImportUriRegion, el->fileNameToken); + } + if (m_loadFileLazily && loadDependencies) { + envPtr->loadPendingDependencies(); + envPtr->commitToBase(qmlFile.environment().item()); + } + + if (el->importToken.isValid()) + FileLocations::addRegion(fileLocation, ImportTokenRegion, el->importToken); + + if (el->asToken.isValid()) + FileLocations::addRegion(fileLocation, AsTokenRegion, el->asToken); + + if (el->importIdToken.isValid()) + FileLocations::addRegion(fileLocation, IdNameRegion, el->importIdToken); + + if (el->version) + FileLocations::addRegion(fileLocation, VersionRegion, combineLocations(el->version)); + + + return true; +} + +bool QQmlDomAstCreator::visit(AST::UiPublicMember *el) +{ + switch (el->type) { + case AST::UiPublicMember::Signal: { + MethodInfo m; + m.name = el->name.toString(); + m.typeName = toString(el->memberType); + m.isReadonly = el->isReadonly(); + m.access = MethodInfo::Public; + m.methodType = MethodInfo::Signal; + m.isList = el->typeModifier == QLatin1String("list"); + MethodInfo *mPtr; + Path p = current<QmlObject>().addMethod(m, AddOption::KeepExisting, &mPtr); + pushEl(p, *mPtr, el); + FileLocations::addRegion(nodeStack.last().fileLocations, SignalKeywordRegion, + el->propertyToken()); + FileLocations::addRegion(nodeStack.last().fileLocations, IdentifierRegion, + el->identifierToken); + MethodInfo &mInfo = std::get<MethodInfo>(currentNode().value); + AST::UiParameterList *args = el->parameters; + while (args) { + MethodParameter param; + param.name = args->name.toString(); + param.typeName = args->type ? args->type->toString() : QString(); + index_type idx = index_type(mInfo.parameters.size()); + mInfo.parameters.append(param); + auto argLocs = FileLocations::ensure(nodeStack.last().fileLocations, + Path::Field(Fields::parameters).index(idx), + AttachedInfo::PathType::Relative); + FileLocations::addRegion(argLocs, MainRegion, combineLocations(args)); + FileLocations::addRegion(argLocs, IdentifierRegion, args->identifierToken); + if (args->type) + FileLocations::addRegion(argLocs, TypeIdentifierRegion, args->propertyTypeToken); + args = args->next; + } + break; + } + case AST::UiPublicMember::Property: { + PropertyDefinition p; + p.name = el->name.toString(); + p.typeName = toString(el->memberType); + p.isReadonly = el->isReadonly(); + p.isDefaultMember = el->isDefaultMember(); + p.isRequired = el->isRequired(); + p.isList = el->typeModifier == QLatin1String("list"); + if (!el->typeModifier.isEmpty()) + p.typeName = el->typeModifier.toString() + QChar(u'<') + p.typeName + QChar(u'>'); + PropertyDefinition *pPtr; + Path pPathFromOwner = + current<QmlObject>().addPropertyDef(p, AddOption::KeepExisting, &pPtr); + if (m_enableScriptExpressions) { + auto qmlObjectType = makeGenericScriptElement(el->memberType, DomType::ScriptType); + qmlObjectType->insertChild(Fields::typeName, + fieldMemberExpressionForQualifiedId(el->memberType)); + pPtr->setNameIdentifiers(finalizeScriptExpression( + ScriptElementVariant::fromElement(qmlObjectType), + pPathFromOwner.field(Fields::nameIdentifiers), rootMap)); + // skip binding identifiers of the binding inside the property definition, if there is + // one + m_skipBindingIdentifiers = el->binding; + } + pushEl(pPathFromOwner, *pPtr, el); + FileLocations::addRegion(nodeStack.last().fileLocations, PropertyKeywordRegion, + el->propertyToken()); + FileLocations::addRegion(nodeStack.last().fileLocations, IdentifierRegion, + el->identifierToken); + FileLocations::addRegion(nodeStack.last().fileLocations, TypeIdentifierRegion, + el->typeToken); + FileLocations::addRegion(nodeStack.last().fileLocations, ColonTokenRegion, el->colonToken); + if (p.name == u"id") + qmlFile.addError(std::move(astParseErrors() + .warning(tr("id is a special attribute, that should not be " + "used as property name")) + .withPath(currentNodeEl().path))); + if (p.isDefaultMember) { + FileLocations::addRegion(nodeStack.last().fileLocations, DefaultKeywordRegion, + el->defaultToken()); + } + if (p.isRequired) { + FileLocations::addRegion(nodeStack.last().fileLocations, RequiredKeywordRegion, + el->requiredToken()); + } + if (p.isReadonly) { + FileLocations::addRegion(nodeStack.last().fileLocations, ReadonlyKeywordRegion, + el->readonlyToken()); + } + if (el->statement) { + BindingType bType = BindingType::Normal; + SourceLocation loc = combineLocations(el->statement); + QStringView code = qmlFilePtr->code(); + + auto script = std::make_shared<ScriptExpression>( + code.mid(loc.offset, loc.length), qmlFilePtr->engine(), el->statement, + qmlFilePtr->astComments(), ScriptExpression::ExpressionType::BindingExpression, + loc); + Binding *bPtr; + Path bPathFromOwner = current<QmlObject>().addBinding(Binding(p.name, script, bType), + AddOption::KeepExisting, &bPtr); + FileLocations::Tree bLoc = createMap(DomType::Binding, bPathFromOwner, el); + FileLocations::addRegion(bLoc, ColonTokenRegion, el->colonToken); + FileLocations::Tree valueLoc = FileLocations::ensure(bLoc, Path::Field(Fields::value), + AttachedInfo::PathType::Relative); + FileLocations::addRegion(valueLoc, MainRegion, combineLocations(el->statement)); + // push it also: its needed in endVisit to add the scriptNode to it + // do not use pushEl to avoid recreating the already created "bLoc" Map + nodeStack.append({ bPathFromOwner, *bPtr, bLoc }); + } + break; + } + } + return true; +} + +void QQmlDomAstCreator::endVisit(AST::UiPublicMember *el) +{ + if (auto &lastEl = currentNode(); lastEl.kind == DomType::Binding) { + Binding &b = std::get<Binding>(lastEl.value); + if (m_enableScriptExpressions + && (scriptNodeStack.size() != 1 || scriptNodeStack.last().isList())) { + Q_SCRIPTELEMENT_DISABLE(); + } + if (m_enableScriptExpressions) { + b.scriptExpressionValue()->setScriptElement(finalizeScriptExpression( + currentScriptNodeEl().takeVariant(), Path().field(Fields::scriptElement), + FileLocations::ensure(currentNodeEl().fileLocations, + Path().field(Fields::value)))); + removeCurrentScriptNode({}); + } + + QmlObject &containingObject = current<QmlObject>(); + Binding *bPtr = + valueFromMultimap(containingObject.m_bindings, b.name(), currentIndex()); + Q_ASSERT(bPtr); + removeCurrentNode({}); + } + Node::accept(el->parameters, this); + loadAnnotations(el); + if ((el->binding || el->statement) + && nodeStack.last().item.kind == DomType::PropertyDefinition) { + PropertyDefinition &pDef = std::get<PropertyDefinition>(nodeStack.last().item.value); + if (!pDef.annotations.isEmpty()) { + QmlObject duplicate; + duplicate.setName(QLatin1String("duplicate")); + QmlObject &obj = current<QmlObject>(); + auto it = obj.m_bindings.find(pDef.name); + if (it != obj.m_bindings.end()) { + for (QmlObject ann : pDef.annotations) { + ann.addAnnotation(duplicate); + it->addAnnotation(currentEl<QmlObject>() + .path.field(Fields::bindings) + .key(pDef.name) + .index(obj.m_bindings.values(pDef.name).size() - 1), + ann); + } + } + } + } + QmlObject &obj = current<QmlObject>(); + QmlStackElement &sEl = nodeStack.last(); + switch (sEl.item.kind) { + case DomType::PropertyDefinition: { + PropertyDefinition pDef = std::get<PropertyDefinition>(sEl.item.value); + PropertyDefinition *pDefPtr = + valueFromMultimap(obj.m_propertyDefs, pDef.name, sEl.path.last().headIndex()); + Q_ASSERT(pDefPtr); + *pDefPtr = pDef; + } break; + case DomType::MethodInfo: { + MethodInfo m = std::get<MethodInfo>(sEl.item.value); + MethodInfo *mPtr = valueFromMultimap(obj.m_methods, m.name, sEl.path.last().headIndex()); + Q_ASSERT(mPtr); + *mPtr = m; + } break; + default: + Q_UNREACHABLE(); + } + removeCurrentNode({}); +} + +bool QQmlDomAstCreator::visit(AST::FunctionDeclaration *fDef) +{ + const QStringView code(qmlFilePtr->code()); + MethodInfo m; + m.name = fDef->name.toString(); + if (AST::TypeAnnotation *tAnn = fDef->typeAnnotation) { + if (AST::Type *t = tAnn->type) + m.typeName = typeToString(t); + } + m.access = MethodInfo::Public; + m.methodType = MethodInfo::Method; + + SourceLocation bodyLoc = fDef->body ? combineLocations(fDef->body) + : combineLocations(fDef->lbraceToken, fDef->rbraceToken); + SourceLocation methodLoc = combineLocations(fDef); + QStringView preCode = code.mid(methodLoc.begin(), bodyLoc.begin() - methodLoc.begin()); + QStringView postCode = code.mid(bodyLoc.end(), methodLoc.end() - bodyLoc.end()); + m.body = std::make_shared<ScriptExpression>( + code.mid(bodyLoc.offset, bodyLoc.length), qmlFilePtr->engine(), fDef->body, + qmlFilePtr->astComments(), ScriptExpression::ExpressionType::FunctionBody, bodyLoc, 0, + preCode, postCode); + + if (fDef->typeAnnotation) { + SourceLocation typeLoc = combineLocations(fDef->typeAnnotation); + m.returnType = std::make_shared<ScriptExpression>( + code.mid(typeLoc.offset, typeLoc.length), qmlFilePtr->engine(), + fDef->typeAnnotation, qmlFilePtr->astComments(), + ScriptExpression::ExpressionType::ReturnType, typeLoc, 0, u"", u""); + } + + MethodInfo *mPtr; + Path mPathFromOwner = current<QmlObject>().addMethod(m, AddOption::KeepExisting, &mPtr); + pushEl(mPathFromOwner, *mPtr, + fDef); // add at the start and use the normal recursive visit? + FileLocations::Tree &fLoc = nodeStack.last().fileLocations; + if (fDef->identifierToken.isValid()) + FileLocations::addRegion(fLoc, IdentifierRegion, fDef->identifierToken); + auto bodyTree = FileLocations::ensure(fLoc, Path::Field(Fields::body), + AttachedInfo::PathType::Relative); + FileLocations::addRegion(bodyTree, MainRegion, bodyLoc); + if (fDef->functionToken.isValid()) + FileLocations::addRegion(fLoc, FunctionKeywordRegion, fDef->functionToken); + if (fDef->lparenToken.length != 0) + FileLocations::addRegion(fLoc, LeftParenthesisRegion, fDef->lparenToken); + if (fDef->rparenToken.length != 0) + FileLocations::addRegion(fLoc, RightParenthesisRegion, fDef->rparenToken); + if (fDef->lbraceToken.length != 0) + FileLocations::addRegion(fLoc, LeftBraceRegion, fDef->lbraceToken); + if (fDef->rbraceToken.length != 0) + FileLocations::addRegion(fLoc, RightBraceRegion, fDef->rbraceToken); + if (fDef->typeAnnotation) + FileLocations::addRegion(fLoc, TypeIdentifierRegion, combineLocations(fDef->typeAnnotation->type)); + MethodInfo &mInfo = std::get<MethodInfo>(currentNode().value); + AST::FormalParameterList *args = fDef->formals; + while (args) { + MethodParameter param; + param.name = args->element->bindingIdentifier.toString(); + if (AST::TypeAnnotation *tAnn = args->element->typeAnnotation) { + if (AST::Type *t = tAnn->type) + param.typeName = typeToString(t); + } + if (args->element->initializer) { + SourceLocation loc = combineLocations(args->element->initializer); + auto script = std::make_shared<ScriptExpression>( + code.mid(loc.offset, loc.length), qmlFilePtr->engine(), + args->element->initializer, qmlFilePtr->astComments(), + ScriptExpression::ExpressionType::ArgInitializer, loc); + param.defaultValue = script; + } + if (args->element->type == AST::PatternElement::SpreadElement) + param.isRestElement = true; + SourceLocation parameterLoc = combineLocations(args->element); + param.value = std::make_shared<ScriptExpression>( + code.mid(parameterLoc.offset, parameterLoc.length), qmlFilePtr->engine(), + args->element, qmlFilePtr->astComments(), + ScriptExpression::ExpressionType::ArgumentStructure, parameterLoc); + + index_type idx = index_type(mInfo.parameters.size()); + mInfo.parameters.append(param); + auto argLocs = FileLocations::ensure(nodeStack.last().fileLocations, + Path::Field(Fields::parameters).index(idx), + AttachedInfo::PathType::Relative); + FileLocations::addRegion(argLocs, MainRegion, combineLocations(args)); + if (args->element->identifierToken.isValid()) + FileLocations::addRegion(argLocs, IdentifierRegion, args->element->identifierToken); + if (args->element->typeAnnotation) + FileLocations::addRegion(argLocs, TypeIdentifierRegion, combineLocations(args->element->typeAnnotation->type)); + args = args->next; + } + return true; +} + +bool QQmlDomAstCreator::visit(AST::UiSourceElement *el) +{ + if (!cast<FunctionDeclaration *>(el->sourceElement)) { + qCWarning(creatorLog) << "unhandled source el:" << static_cast<AST::Node *>(el); + Q_UNREACHABLE(); + } + return true; +} + +static void setFormalParameterKind(ScriptElementVariant &variant) +{ + if (auto data = variant.data()) { + if (auto genericElement = + std::get_if<std::shared_ptr<ScriptElements::GenericScriptElement>>(&*data)) { + (*genericElement)->setKind(DomType::ScriptFormalParameter); + } + } +} + +void QQmlDomAstCreator::endVisit(AST::FunctionDeclaration *fDef) +{ + MethodInfo &m = std::get<MethodInfo>(currentNode().value); + const FileLocations::Tree bodyTree = + FileLocations::ensure(currentNodeEl().fileLocations, Path().field(Fields::body)); + const Path bodyPath = Path().field(Fields::scriptElement); + + if (!m_enableScriptExpressions) + return; + + if (fDef->body) { + Q_SCRIPTELEMENT_EXIT_IF(scriptNodeStack.isEmpty()); + if (currentScriptNodeEl().isList()) { + // It is more intuitive to have functions with a block as a body instead of a + // list. + auto body = std::make_shared<ScriptElements::BlockStatement>( + combineLocations(fDef->lbraceToken, fDef->rbraceToken)); + body->setStatements(currentScriptNodeEl().takeList()); + if (auto semanticScope = body->statements().semanticScope()) + body->setSemanticScope(semanticScope); + m.body->setScriptElement(finalizeScriptExpression( + ScriptElementVariant::fromElement(body), bodyPath, bodyTree)); + } else { + m.body->setScriptElement(finalizeScriptExpression( + currentScriptNodeEl().takeVariant(), bodyPath, bodyTree)); + } + removeCurrentScriptNode({}); + } else { + // for convenience purposes: insert an empty BlockStatement + auto body = std::make_shared<ScriptElements::BlockStatement>( + combineLocations(fDef->lbraceToken, fDef->rbraceToken)); + m.body->setScriptElement(finalizeScriptExpression(ScriptElementVariant::fromElement(body), + bodyPath, bodyTree)); + } + + if (fDef->typeAnnotation) { + auto argLoc = FileLocations::ensure(nodeStack.last().fileLocations, + Path().field(Fields::returnType), + AttachedInfo::PathType::Relative); + const Path pathToReturnType = Path().field(Fields::scriptElement); + + Q_SCRIPTELEMENT_EXIT_IF(scriptNodeStack.isEmpty() || scriptNodeStack.last().isList()); + ScriptElementVariant variant = currentScriptNodeEl().takeVariant(); + finalizeScriptExpression(variant, pathToReturnType, argLoc); + m.returnType->setScriptElement(variant); + removeCurrentScriptNode({}); + } + std::vector<FormalParameterList *> reversedInitializerExpressions; + for (auto it = fDef->formals; it; it = it->next) { + reversedInitializerExpressions.push_back(it); + } + const size_t size = reversedInitializerExpressions.size(); + for (size_t idx = size - 1; idx < size; --idx) { + auto argLoc = FileLocations::ensure( + nodeStack.last().fileLocations, + Path().field(Fields::parameters).index(idx).field(Fields::value), + AttachedInfo::PathType::Relative); + const Path pathToArgument = Path().field(Fields::scriptElement); + + Q_SCRIPTELEMENT_EXIT_IF(scriptNodeStack.isEmpty() || scriptNodeStack.last().isList()); + ScriptElementVariant variant = currentScriptNodeEl().takeVariant(); + setFormalParameterKind(variant); + finalizeScriptExpression(variant, pathToArgument, argLoc); + m.parameters[idx].value->setScriptElement(variant); + removeCurrentScriptNode({}); + } + + // there should be no more uncollected script elements + if (m_enableScriptExpressions && !scriptNodeStack.empty()) { + Q_SCRIPTELEMENT_DISABLE(); + } +} + +void QQmlDomAstCreator::endVisit(AST::UiSourceElement *el) +{ + MethodInfo &m = std::get<MethodInfo>(currentNode().value); + loadAnnotations(el); + QmlObject &obj = current<QmlObject>(); + MethodInfo *mPtr = + valueFromMultimap(obj.m_methods, m.name, nodeStack.last().path.last().headIndex()); + Q_ASSERT(mPtr); + *mPtr = m; + removeCurrentNode(DomType::MethodInfo); +} + +bool QQmlDomAstCreator::visit(AST::UiObjectDefinition *el) +{ + QmlObject scope; + scope.setName(toString(el->qualifiedTypeNameId)); + scope.addPrototypePath(Paths::lookupTypePath(scope.name())); + QmlObject *sPtr = nullptr; + Path sPathFromOwner; + if (!arrayBindingLevels.isEmpty() && nodeStack.size() == arrayBindingLevels.last()) { + if (currentNode().kind == DomType::Binding) { + QList<QmlObject> *vals = std::get<Binding>(currentNode().value).arrayValue(); + if (vals) { + int idx = vals->size(); + vals->append(scope); + sPathFromOwner = currentNodeEl().path.field(Fields::value).index(idx); + sPtr = &((*vals)[idx]); + sPtr->updatePathFromOwner(sPathFromOwner); + } else { + Q_ASSERT_X(false, className, + "expected an array binding with a valid QList<QmlScope> as value"); + } + } else { + Q_ASSERT_X(false, className, "expected an array binding as last node on the stack"); + } + } else { + DomValue &containingObject = currentQmlObjectOrComponentEl().item; + switch (containingObject.kind) { + case DomType::QmlComponent: + sPathFromOwner = std::get<QmlComponent>(containingObject.value).addObject(scope, &sPtr); + break; + case DomType::QmlObject: + sPathFromOwner = std::get<QmlObject>(containingObject.value).addChild(scope, &sPtr); + break; + default: + Q_UNREACHABLE(); + } + Path pathFromContainingObject = sPathFromOwner.mid(currentNodeEl().path.length()); + FileLocations::Tree fLoc = + FileLocations::ensure(currentNodeEl().fileLocations, pathFromContainingObject, + AttachedInfo::PathType::Relative); + FileLocations::addRegion(fLoc, IdentifierRegion, + el->qualifiedTypeNameId->identifierToken); + } + Q_ASSERT_X(sPtr, className, "could not recover new scope"); + + if (m_enableScriptExpressions) { + auto qmlObjectType = makeGenericScriptElement(el->qualifiedTypeNameId, DomType::ScriptType); + qmlObjectType->insertChild(Fields::typeName, + fieldMemberExpressionForQualifiedId(el->qualifiedTypeNameId)); + sPtr->setNameIdentifiers( + finalizeScriptExpression(ScriptElementVariant::fromElement(qmlObjectType), + sPathFromOwner.field(Fields::nameIdentifiers), rootMap)); + } + pushEl(sPathFromOwner, *sPtr, el); + + if (m_enableScriptExpressions && el->initializer) { + FileLocations::addRegion(nodeStack.last().fileLocations, LeftBraceRegion, + el->initializer->lbraceToken); + FileLocations::addRegion(nodeStack.last().fileLocations, RightBraceRegion, + el->initializer->rbraceToken); + } + loadAnnotations(el); + return true; +} + +void QQmlDomAstCreator::endVisit(AST::UiObjectDefinition *) +{ + QmlObject &obj = current<QmlObject>(); + int idx = currentIndex(); + if (!arrayBindingLevels.isEmpty() && nodeStack.size() == arrayBindingLevels.last() + 1) { + if (currentNode(1).kind == DomType::Binding) { + Binding &b = std::get<Binding>(currentNode(1).value); + QList<QmlObject> *vals = b.arrayValue(); + Q_ASSERT_X(vals, className, + "expected an array binding with a valid QList<QmlScope> as value"); + (*vals)[idx] = obj; + } else { + Q_ASSERT_X(false, className, "expected an array binding as last node on the stack"); + } + } else { + DomValue &containingObject = currentNodeEl(1).item; + Path p = currentNodeEl().path; + switch (containingObject.kind) { + case DomType::QmlComponent: + if (p[p.length() - 2] == Path::Field(Fields::objects)) + std::get<QmlComponent>(containingObject.value).m_objects[idx] = obj; + else + Q_UNREACHABLE(); + break; + case DomType::QmlObject: + if (p[p.length() - 2] == Path::Field(Fields::children)) + std::get<QmlObject>(containingObject.value).m_children[idx] = obj; + else + Q_UNREACHABLE(); + break; + default: + Q_UNREACHABLE(); + } + } + removeCurrentNode(DomType::QmlObject); +} + +void QQmlDomAstCreator::setBindingIdentifiers(const Path &pathFromOwner, + const UiQualifiedId *identifiers, Binding *bindingPtr) +{ + const bool skipBindingIdentifiers = std::exchange(m_skipBindingIdentifiers, false); + if (!m_enableScriptExpressions || skipBindingIdentifiers) + return; + + ScriptElementVariant bindable = fieldMemberExpressionForQualifiedId(identifiers); + bindingPtr->setBindingIdentifiers(finalizeScriptExpression( + bindable, pathFromOwner.field(Fields::bindingIdentifiers), rootMap)); +} + +bool QQmlDomAstCreator::visit(AST::UiObjectBinding *el) +{ + BindingType bType = (el->hasOnToken ? BindingType::OnBinding : BindingType::Normal); + QmlObject value; + value.setName(toString(el->qualifiedTypeNameId)); + Binding *bPtr; + Path bPathFromOwner = current<QmlObject>().addBinding( + Binding(toString(el->qualifiedId), value, bType), AddOption::KeepExisting, &bPtr); + if (bPtr->name() == u"id") + qmlFile.addError(std::move(astParseErrors() + .warning(tr("id attributes should only be a lower case letter " + "followed by letters, numbers or underscore, " + "assuming they refer to an id property")) + .withPath(bPathFromOwner))); + setBindingIdentifiers(bPathFromOwner, el->qualifiedId, bPtr); + + pushEl(bPathFromOwner, *bPtr, el); + if (el->hasOnToken) + FileLocations::addRegion(nodeStack.last().fileLocations, OnTokenRegion, el->colonToken); + else + FileLocations::addRegion(nodeStack.last().fileLocations, ColonTokenRegion, el->colonToken); + FileLocations::addRegion(nodeStack.last().fileLocations, IdentifierRegion, combineLocations(el->qualifiedId)); + loadAnnotations(el); + QmlObject *objValue = bPtr->objectValue(); + Q_ASSERT_X(objValue, className, "could not recover objectValue"); + objValue->setName(toString(el->qualifiedTypeNameId)); + + if (m_enableScriptExpressions) { + auto qmlObjectType = makeGenericScriptElement(el->qualifiedTypeNameId, DomType::ScriptType); + qmlObjectType->insertChild(Fields::typeName, + fieldMemberExpressionForQualifiedId(el->qualifiedTypeNameId)); + objValue->setNameIdentifiers(finalizeScriptExpression( + ScriptElementVariant::fromElement(qmlObjectType), + bPathFromOwner.field(Fields::value).field(Fields::nameIdentifiers), rootMap)); + } + + objValue->addPrototypePath(Paths::lookupTypePath(objValue->name())); + pushEl(bPathFromOwner.field(Fields::value), *objValue, el->initializer); + if (m_enableScriptExpressions && el->initializer) { + FileLocations::addRegion(nodeStack.last().fileLocations, LeftBraceRegion, + el->initializer->lbraceToken); + FileLocations::addRegion(nodeStack.last().fileLocations, RightBraceRegion, + el->initializer->rbraceToken); + } + return true; +} + +void QQmlDomAstCreator::endVisit(AST::UiObjectBinding *) +{ + QmlObject &objValue = current<QmlObject>(); + QmlObject &containingObj = current<QmlObject>(1); + Binding &b = std::get<Binding>(currentNode(1).value); + QmlObject *objPtr = b.objectValue(); + Q_ASSERT(objPtr); + *objPtr = objValue; + index_type idx = currentNodeEl(1).path.last().headIndex(); + Binding *bPtr = valueFromMultimap(containingObj.m_bindings, b.name(), idx); + Q_ASSERT(bPtr); + *bPtr = b; + removeCurrentNode(DomType::QmlObject); + removeCurrentNode(DomType::Binding); +} + +bool QQmlDomAstCreator::visit(AST::UiScriptBinding *el) +{ + QStringView code = qmlFilePtr->code(); + SourceLocation loc = combineLocations(el->statement); + auto script = std::make_shared<ScriptExpression>( + code.mid(loc.offset, loc.length), qmlFilePtr->engine(), el->statement, + qmlFilePtr->astComments(), ScriptExpression::ExpressionType::BindingExpression, loc); + Binding bindingV(toString(el->qualifiedId), script, BindingType::Normal); + Binding *bindingPtr = nullptr; + Id *idPtr = nullptr; + Path pathFromOwner; + if (bindingV.name() == u"id") { + Node *exp = script->ast(); + if (ExpressionStatement *eStat = cast<ExpressionStatement *>(script->ast())) + exp = eStat->expression; + if (IdentifierExpression *iExp = cast<IdentifierExpression *>(exp)) { + QmlStackElement &containingObjectEl = currentEl<QmlObject>(); + QmlObject &containingObject = std::get<QmlObject>(containingObjectEl.item.value); + QString idName = iExp->name.toString(); + Id idVal(idName, qmlFile.canonicalPath().path(containingObject.pathFromOwner())); + idVal.value = script; + containingObject.setIdStr(idName); + FileLocations::addRegion(containingObjectEl.fileLocations, IdTokenRegion, + combineLocations(el->qualifiedId)); + FileLocations::addRegion(containingObjectEl.fileLocations, IdColonTokenRegion, + el->colonToken); + FileLocations::addRegion(containingObjectEl.fileLocations, IdNameRegion, + combineLocations(el->statement)); + QmlComponent &comp = current<QmlComponent>(); + pathFromOwner = comp.addId(idVal, AddOption::KeepExisting, &idPtr); + QRegularExpression idRe(QRegularExpression::anchoredPattern( + QStringLiteral(uR"([[:lower:]][[:lower:][:upper:]0-9_]*)"))); + auto m = idRe.matchView(iExp->name); + if (!m.hasMatch()) { + qmlFile.addError(std::move( + astParseErrors() + .warning(tr("id attributes should only be a lower case letter " + "followed by letters, numbers or underscore, not %1") + .arg(iExp->name)) + .withPath(pathFromOwner))); + } + } else { + pathFromOwner = + current<QmlObject>().addBinding(bindingV, AddOption::KeepExisting, &bindingPtr); + Q_ASSERT_X(bindingPtr, className, "binding could not be retrieved"); + qmlFile.addError(std::move( + astParseErrors() + .warning(tr("id attributes should only be a lower case letter " + "followed by letters, numbers or underscore, not %1 " + "%2, assuming they refer to a property") + .arg(script->code(), script->astRelocatableDump())) + .withPath(pathFromOwner))); + } + } else { + pathFromOwner = + current<QmlObject>().addBinding(bindingV, AddOption::KeepExisting, &bindingPtr); + QmlStackElement &containingObjectEl = currentEl<QmlObject>(); + // remove the containingObjectEl.path prefix from pathFromOwner + Path pathFromContainingObject = pathFromOwner.mid(containingObjectEl.path.length()); + auto bindingFileLocation = + FileLocations::ensure(containingObjectEl.fileLocations, pathFromContainingObject); + FileLocations::addRegion(bindingFileLocation, IdentifierRegion, + el->qualifiedId->identifierToken); + FileLocations::addRegion(bindingFileLocation, ColonTokenRegion, el->colonToken); + + setBindingIdentifiers(pathFromOwner, el->qualifiedId, bindingPtr); + + Q_ASSERT_X(bindingPtr, className, "binding could not be retrieved"); + } + if (bindingPtr) + pushEl(pathFromOwner, *bindingPtr, el); + else if (idPtr) + pushEl(pathFromOwner, *idPtr, el); + else + Q_UNREACHABLE(); + loadAnnotations(el); + // avoid duplicate colon location for id? + FileLocations::addRegion(nodeStack.last().fileLocations, ColonTokenRegion, el->colonToken); + return true; +} + +void QQmlDomAstCreator::setScriptExpression (const std::shared_ptr<ScriptExpression>& value) +{ + if (m_enableScriptExpressions + && (scriptNodeStack.size() != 1 || currentScriptNodeEl().isList())) + Q_SCRIPTELEMENT_DISABLE(); + if (m_enableScriptExpressions) { + FileLocations::Tree valueLoc = FileLocations::ensure(currentNodeEl().fileLocations, + Path().field(Fields::value)); + value->setScriptElement(finalizeScriptExpression(currentScriptNodeEl().takeVariant(), + Path().field(Fields::scriptElement), + valueLoc)); + removeCurrentScriptNode({}); + } +}; + +void QQmlDomAstCreator::endVisit(AST::UiScriptBinding *) +{ + DomValue &lastEl = currentNode(); + index_type idx = currentIndex(); + if (lastEl.kind == DomType::Binding) { + Binding &b = std::get<Binding>(lastEl.value); + + setScriptExpression(b.scriptExpressionValue()); + + QmlObject &containingObject = current<QmlObject>(); + Binding *bPtr = valueFromMultimap(containingObject.m_bindings, b.name(), idx); + Q_ASSERT(bPtr); + *bPtr = b; + } else if (lastEl.kind == DomType::Id) { + Id &id = std::get<Id>(lastEl.value); + + setScriptExpression(id.value); + + QmlComponent &comp = current<QmlComponent>(); + Id *idPtr = valueFromMultimap(comp.m_ids, id.name, idx); + *idPtr = id; + } else { + Q_UNREACHABLE(); + } + + // there should be no more uncollected script elements + if (m_enableScriptExpressions && !scriptNodeStack.empty()) { + Q_SCRIPTELEMENT_DISABLE(); + } + removeCurrentNode({}); +} + +bool QQmlDomAstCreator::visit(AST::UiArrayBinding *el) +{ + QList<QmlObject> value; + Binding bindingV(toString(el->qualifiedId), value, BindingType::Normal); + Binding *bindingPtr; + Path bindingPathFromOwner = + current<QmlObject>().addBinding(bindingV, AddOption::KeepExisting, &bindingPtr); + if (bindingV.name() == u"id") + qmlFile.addError(std::move( + astParseErrors() + .error(tr("id attributes should have only simple strings as values")) + .withPath(bindingPathFromOwner))); + + setBindingIdentifiers(bindingPathFromOwner, el->qualifiedId, bindingPtr); + + pushEl(bindingPathFromOwner, *bindingPtr, el); + FileLocations::addRegion(currentNodeEl().fileLocations, ColonTokenRegion, el->colonToken); + loadAnnotations(el); + FileLocations::Tree arrayList = + createMap(currentNodeEl().fileLocations, Path::Field(Fields::value), nullptr); + FileLocations::addRegion(arrayList, LeftBracketRegion, el->lbracketToken); + FileLocations::addRegion(arrayList, RightBracketRegion, el->rbracketToken); + arrayBindingLevels.append(nodeStack.size()); + return true; +} + +void QQmlDomAstCreator::endVisit(AST::UiArrayBinding *) +{ + index_type idx = currentIndex(); + Binding &b = std::get<Binding>(currentNode().value); + Binding *bPtr = valueFromMultimap(current<QmlObject>().m_bindings, b.name(), idx); + *bPtr = b; + arrayBindingLevels.removeLast(); + removeCurrentNode(DomType::Binding); +} + +bool QQmlDomAstCreator::visit(AST::ArgumentList *list) +{ + if (!m_enableScriptExpressions) + return false; + + auto currentList = makeScriptList(list); + + for (auto it = list; it; it = it->next) { + Node::accept(it->expression, this); + if (!m_enableScriptExpressions) + return false; + + if (scriptNodeStack.empty() || scriptNodeStack.last().isList()) { + Q_SCRIPTELEMENT_DISABLE(); + return false; + } + currentList.append(scriptNodeStack.last().takeVariant()); + scriptNodeStack.removeLast(); + } + + pushScriptElement(currentList); + + return false; // return false because we already iterated over the children using the custom + // iteration above +} + +bool QQmlDomAstCreator::visit(AST::UiParameterList *) +{ + return false; // do not create script node for Ui stuff +} + +bool QQmlDomAstCreator::visit(AST::PatternElementList *list) +{ + if (!m_enableScriptExpressions) + return false; + + auto currentList = makeScriptList(list); + + for (auto it = list; it; it = it->next) { + if (it->elision) { + Node::accept(it->elision, this); + if (scriptNodeStack.empty() || !scriptNodeStack.last().isList()) { + Q_SCRIPTELEMENT_DISABLE(); + return false; + } + currentList.append(scriptNodeStack.last().takeList()); + scriptNodeStack.removeLast(); + } + if (it->element) { + Node::accept(it->element, this); + if (scriptNodeStack.empty() || scriptNodeStack.last().isList()) { + Q_SCRIPTELEMENT_DISABLE(); + return false; + } + currentList.append(scriptNodeStack.last().takeVariant()); + scriptNodeStack.removeLast(); + } + } + + pushScriptElement(currentList); + + return false; // return false because we already iterated over the children using the custom + // iteration above +} + +bool QQmlDomAstCreator::visit(AST::PatternPropertyList *list) +{ + if (!m_enableScriptExpressions) + return false; + + auto currentList = makeScriptList(list); + + for (auto it = list; it; it = it->next) { + if (it->property) { + Node::accept(it->property, this); + if (!m_enableScriptExpressions) + return false; + if (scriptNodeStack.empty() || scriptNodeStack.last().isList()) { + Q_SCRIPTELEMENT_DISABLE(); + return false; + } + currentList.append(scriptNodeStack.last().takeVariant()); + scriptNodeStack.removeLast(); + } + } + + pushScriptElement(currentList); + + return false; // return false because we already iterated over the children using the custom + // iteration above +} + +/*! + \internal + Implementing the logic of this method in \c QQmlDomAstCreator::visit(AST::UiQualifiedId *) + would create scriptelements at places where there are not needed. This is mainly because + UiQualifiedId's appears inside and outside of script parts. +*/ +ScriptElementVariant QQmlDomAstCreator::scriptElementForQualifiedId(AST::UiQualifiedId *expression) +{ + auto id = std::make_shared<ScriptElements::IdentifierExpression>( + expression->firstSourceLocation(), expression->lastSourceLocation()); + id->setName(expression->toString()); + + return ScriptElementVariant::fromElement(id); +} + +bool QQmlDomAstCreator::visit(AST::UiQualifiedId *) +{ + if (!m_enableScriptExpressions) + return false; + + return false; +} + +bool QQmlDomAstCreator::visit(AST::UiEnumDeclaration *el) +{ + EnumDecl eDecl; + eDecl.setName(el->name.toString()); + EnumDecl *ePtr; + Path enumPathFromOwner = + current<QmlComponent>().addEnumeration(eDecl, AddOption::KeepExisting, &ePtr); + pushEl(enumPathFromOwner, *ePtr, el); + FileLocations::addRegion(nodeStack.last().fileLocations, EnumKeywordRegion, el->enumToken); + FileLocations::addRegion(nodeStack.last().fileLocations, IdentifierRegion, el->identifierToken); + loadAnnotations(el); + return true; +} + +void QQmlDomAstCreator::endVisit(AST::UiEnumDeclaration *) +{ + EnumDecl &e = std::get<EnumDecl>(currentNode().value); + EnumDecl *ePtr = + valueFromMultimap(current<QmlComponent>().m_enumerations, e.name(), currentIndex()); + Q_ASSERT(ePtr); + *ePtr = e; + removeCurrentNode(DomType::EnumDecl); +} + +bool QQmlDomAstCreator::visit(AST::UiEnumMemberList *el) +{ + EnumItem it(el->member.toString(), el->value); + EnumDecl &eDecl = std::get<EnumDecl>(currentNode().value); + Path itPathFromDecl = eDecl.addValue(it); + const auto map = createMap(DomType::EnumItem, itPathFromDecl, nullptr); + FileLocations::addRegion(map, MainRegion, combine(el->memberToken, el->valueToken)); + if (el->memberToken.isValid()) + FileLocations::addRegion(map, IdentifierRegion, el->memberToken); + if (el->valueToken.isValid()) + FileLocations::addRegion(map, EnumValueRegion, el->valueToken); + return true; +} + +void QQmlDomAstCreator::endVisit(AST::UiEnumMemberList *el) +{ + Node::accept(el->next, this); // put other enum members at the same level as this one... +} + +bool QQmlDomAstCreator::visit(AST::UiInlineComponent *el) +{ + QStringList els = current<QmlComponent>().name().split(QLatin1Char('.')); + els.append(el->name.toString()); + QString cName = els.join(QLatin1Char('.')); + QmlComponent *compPtr; + Path p = qmlFilePtr->addComponent(QmlComponent(cName), AddOption::KeepExisting, &compPtr); + + if (m_enableScriptExpressions) { + auto inlineComponentType = + makeGenericScriptElement(el->identifierToken, DomType::ScriptType); + + auto typeName = std::make_shared<ScriptElements::IdentifierExpression>(el->identifierToken); + typeName->setName(el->name); + inlineComponentType->insertChild(Fields::typeName, + ScriptElementVariant::fromElement(typeName)); + compPtr->setNameIdentifiers( + finalizeScriptExpression(ScriptElementVariant::fromElement(inlineComponentType), + p.field(Fields::nameIdentifiers), rootMap)); + } + + pushEl(p, *compPtr, el); + FileLocations::addRegion(nodeStack.last().fileLocations, ComponentKeywordRegion, + el->componentToken); + FileLocations::addRegion(nodeStack.last().fileLocations, IdentifierRegion, el->identifierToken); + loadAnnotations(el); + return true; +} + +void QQmlDomAstCreator::endVisit(AST::UiInlineComponent *) +{ + QmlComponent &component = std::get<QmlComponent>(currentNode().value); + QStringList nameEls = component.name().split(QChar::fromLatin1('.')); + QString key = nameEls.mid(1).join(QChar::fromLatin1('.')); + QmlComponent *cPtr = valueFromMultimap(qmlFilePtr->lazyMembers().m_components, key, currentIndex()); + Q_ASSERT(cPtr); + *cPtr = component; + removeCurrentNode(DomType::QmlComponent); +} + +bool QQmlDomAstCreator::visit(UiRequired *el) +{ + PropertyDefinition pDef; + pDef.name = el->name.toString(); + pDef.isRequired = true; + PropertyDefinition *pDefPtr; + Path pathFromOwner = + current<QmlObject>().addPropertyDef(pDef, AddOption::KeepExisting, &pDefPtr); + createMap(DomType::PropertyDefinition, pathFromOwner, el); + return false; +} + +bool QQmlDomAstCreator::visit(AST::UiAnnotation *el) +{ + QmlObject a; + a.setName(QStringLiteral(u"@") + toString(el->qualifiedTypeNameId)); + // add annotation prototype? + DomValue &containingElement = currentNode(); + Path pathFromOwner; + QmlObject *aPtr = nullptr; + switch (containingElement.kind) { + case DomType::QmlObject: + pathFromOwner = std::get<QmlObject>(containingElement.value).addAnnotation(a, &aPtr); + break; + case DomType::Binding: + pathFromOwner = std::get<Binding>(containingElement.value) + .addAnnotation(currentNodeEl().path, a, &aPtr); + break; + case DomType::Id: + pathFromOwner = + std::get<Id>(containingElement.value).addAnnotation(currentNodeEl().path, a, &aPtr); + break; + case DomType::PropertyDefinition: + pathFromOwner = std::get<PropertyDefinition>(containingElement.value) + .addAnnotation(currentNodeEl().path, a, &aPtr); + break; + case DomType::MethodInfo: + pathFromOwner = std::get<MethodInfo>(containingElement.value) + .addAnnotation(currentNodeEl().path, a, &aPtr); + break; + default: + qCWarning(domLog) << "Unexpected container object for annotation:" + << domTypeToString(containingElement.kind); + Q_UNREACHABLE(); + } + pushEl(pathFromOwner, *aPtr, el); + return true; +} + +void QQmlDomAstCreator::endVisit(AST::UiAnnotation *) +{ + DomValue &containingElement = currentNode(1); + Path pathFromOwner; + QmlObject &a = std::get<QmlObject>(currentNode().value); + switch (containingElement.kind) { + case DomType::QmlObject: + std::get<QmlObject>(containingElement.value).m_annotations[currentIndex()] = a; + break; + case DomType::Binding: + std::get<Binding>(containingElement.value).m_annotations[currentIndex()] = a; + break; + case DomType::Id: + std::get<Id>(containingElement.value).annotations[currentIndex()] = a; + break; + case DomType::PropertyDefinition: + std::get<PropertyDefinition>(containingElement.value).annotations[currentIndex()] = a; + break; + case DomType::MethodInfo: + std::get<MethodInfo>(containingElement.value).annotations[currentIndex()] = a; + break; + default: + Q_UNREACHABLE(); + } + removeCurrentNode(DomType::QmlObject); +} + +void QQmlDomAstCreator::throwRecursionDepthError() +{ + qmlFile.addError(astParseErrors().error( + tr("Maximum statement or expression depth exceeded in QmlDomAstCreator"))); +} + +bool QQmlDomAstCreator::visit(AST::StatementList *) +{ + if (!m_enableScriptExpressions) + return false; + + return true; +} + +void QQmlDomAstCreator::endVisit(AST::StatementList *list) +{ + if (!m_enableScriptExpressions) + return; + + auto current = makeScriptList(list); + + for (auto it = list; it; it = it->next) { + Q_SCRIPTELEMENT_EXIT_IF(scriptNodeStack.isEmpty() || scriptNodeStack.last().isList()); + current.append(scriptNodeStack.takeLast().takeVariant()); + } + + current.reverse(); + pushScriptElement(current); +} + +bool QQmlDomAstCreator::visit(AST::BinaryExpression *) +{ + if (!m_enableScriptExpressions) + return false; + + return true; +} + +void QQmlDomAstCreator::endVisit(AST::BinaryExpression *exp) +{ + if (!m_enableScriptExpressions) + return; + + auto current = makeScriptElement<ScriptElements::BinaryExpression>(exp); + current->addLocation(OperatorTokenRegion, exp->operatorToken); + Q_SCRIPTELEMENT_EXIT_IF(scriptNodeStack.isEmpty() || scriptNodeStack.last().isList()); + current->setRight(currentScriptNodeEl().takeVariant()); + removeCurrentScriptNode({}); + Q_SCRIPTELEMENT_EXIT_IF(scriptNodeStack.isEmpty() || scriptNodeStack.last().isList()); + current->setLeft(currentScriptNodeEl().takeVariant()); + removeCurrentScriptNode({}); + + pushScriptElement(current); +} + +bool QQmlDomAstCreator::visit(AST::Block *) +{ + if (!m_enableScriptExpressions) + return false; + + return true; +} + +void QQmlDomAstCreator::endVisit(AST::Block *block) +{ + if (!m_enableScriptExpressions) + return; + + auto current = makeScriptElement<ScriptElements::BlockStatement>(block); + + if (block->statements) { + Q_SCRIPTELEMENT_EXIT_IF(scriptNodeStack.isEmpty()); + current->setStatements(currentScriptNodeEl().takeList()); + removeCurrentScriptNode(DomType::List); + } + + pushScriptElement(current); +} + +bool QQmlDomAstCreator::visit(AST::ForStatement *) +{ + if (!m_enableScriptExpressions) + return false; + + return true; +} + +void QQmlDomAstCreator::endVisit(AST::ForStatement *forStatement) +{ + if (!m_enableScriptExpressions) + return; + + auto current = makeScriptElement<ScriptElements::ForStatement>(forStatement); + current->addLocation(FileLocationRegion::ForKeywordRegion, forStatement->forToken); + current->addLocation(FileLocationRegion::LeftParenthesisRegion, forStatement->lparenToken); + current->addLocation(FileLocationRegion::FirstSemicolonTokenRegion, + forStatement->firstSemicolonToken); + current->addLocation(FileLocationRegion::SecondSemicolonRegion, + forStatement->secondSemicolonToken); + current->addLocation(FileLocationRegion::RightParenthesisRegion, forStatement->rparenToken); + + if (forStatement->statement) { + Q_SCRIPTELEMENT_EXIT_IF(scriptNodeStack.isEmpty() || scriptNodeStack.last().isList()); + current->setBody(currentScriptNodeEl().takeVariant()); + removeCurrentScriptNode(std::nullopt); + } + + if (forStatement->expression) { + Q_SCRIPTELEMENT_EXIT_IF(scriptNodeStack.isEmpty() || scriptNodeStack.last().isList()); + current->setExpression(currentScriptNodeEl().takeVariant()); + removeCurrentScriptNode(std::nullopt); + } + + if (forStatement->condition) { + Q_SCRIPTELEMENT_EXIT_IF(scriptNodeStack.isEmpty() || scriptNodeStack.last().isList()); + current->setCondition(currentScriptNodeEl().takeVariant()); + removeCurrentScriptNode(std::nullopt); + } + + if (forStatement->declarations) { + Q_SCRIPTELEMENT_EXIT_IF(scriptNodeStack.isEmpty()); + auto variableDeclaration = makeGenericScriptElement(forStatement->declarations, + DomType::ScriptVariableDeclaration); + + ScriptElements::ScriptList list = currentScriptNodeEl().takeList(); + list.replaceKindForGenericChildren(DomType::ScriptPattern, + DomType::ScriptVariableDeclarationEntry); + variableDeclaration->insertChild(Fields::declarations, std::move(list)); + removeCurrentScriptNode({}); + + current->setDeclarations(ScriptElementVariant::fromElement(variableDeclaration)); + + if (auto pe = forStatement->declarations->declaration; + pe && pe->declarationKindToken.isValid()) { + current->addLocation(FileLocationRegion::TypeIdentifierRegion, + pe->declarationKindToken); + } + } + + if (forStatement->initialiser) { + Q_SCRIPTELEMENT_EXIT_IF(scriptNodeStack.isEmpty() || scriptNodeStack.last().isList()); + current->setInitializer(currentScriptNodeEl().takeVariant()); + removeCurrentScriptNode(std::nullopt); + } + pushScriptElement(current); +} + +bool QQmlDomAstCreator::visit(AST::IdentifierExpression *expression) +{ + if (!m_enableScriptExpressions) + return false; + + auto current = makeScriptElement<ScriptElements::IdentifierExpression>(expression); + current->setName(expression->name); + pushScriptElement(current); + return true; +} + +bool QQmlDomAstCreator::visit(AST::NumericLiteral *expression) +{ + if (!m_enableScriptExpressions) + return false; + + auto current = makeScriptElement<ScriptElements::Literal>(expression); + current->setLiteralValue(expression->value); + pushScriptElement(current); + return true; +} + +bool QQmlDomAstCreator::visit(AST::StringLiteral *expression) +{ + if (!m_enableScriptExpressions) + return false; + + pushScriptElement(makeStringLiteral(expression->value, expression)); + return true; +} + +bool QQmlDomAstCreator::visit(AST::NullExpression *expression) +{ + if (!m_enableScriptExpressions) + return false; + + auto current = makeScriptElement<ScriptElements::Literal>(expression); + current->setLiteralValue(nullptr); + pushScriptElement(current); + return true; +} + +bool QQmlDomAstCreator::visit(AST::TrueLiteral *expression) +{ + if (!m_enableScriptExpressions) + return false; + + auto current = makeScriptElement<ScriptElements::Literal>(expression); + current->setLiteralValue(true); + pushScriptElement(current); + return true; +} + +bool QQmlDomAstCreator::visit(AST::FalseLiteral *expression) +{ + if (!m_enableScriptExpressions) + return false; + + auto current = makeScriptElement<ScriptElements::Literal>(expression); + current->setLiteralValue(false); + pushScriptElement(current); + return true; +} + +bool QQmlDomAstCreator::visit(AST::IdentifierPropertyName *expression) +{ + if (!m_enableScriptExpressions) + return false; + + auto current = makeScriptElement<ScriptElements::IdentifierExpression>(expression); + current->setName(expression->id); + pushScriptElement(current); + return true; +} + +bool QQmlDomAstCreator::visit(AST::StringLiteralPropertyName *expression) +{ + if (!m_enableScriptExpressions) + return false; + + pushScriptElement(makeStringLiteral(expression->id, expression)); + return true; +} + +bool QQmlDomAstCreator::visit(AST::TypeAnnotation *) +{ + if (!m_enableScriptExpressions) + return false; + + // do nothing: the work is done in (end)visit(AST::Type*). + return true; +} + +bool QQmlDomAstCreator::visit(AST::NumericLiteralPropertyName *expression) +{ + if (!m_enableScriptExpressions) + return false; + + auto current = makeScriptElement<ScriptElements::Literal>(expression); + current->setLiteralValue(expression->id); + pushScriptElement(current); + return true; +} + +bool QQmlDomAstCreator::visit(AST::ComputedPropertyName *) +{ + if (!m_enableScriptExpressions) + return false; + + // nothing to do, just forward the underlying expression without changing/wrapping it + return true; +} + +bool QQmlDomAstCreator::visit(AST::VariableDeclarationList *list) +{ + if (!m_enableScriptExpressions) + return false; + + auto currentList = makeScriptList(list); + + for (auto it = list; it; it = it->next) { + if (it->declaration) { + Node::accept(it->declaration, this); + if (!m_enableScriptExpressions) + return false; + if (scriptNodeStack.empty() || scriptNodeStack.last().isList()) { + Q_SCRIPTELEMENT_DISABLE(); + return false; + } + currentList.append(scriptNodeStack.last().takeVariant()); + scriptNodeStack.removeLast(); + } + } + pushScriptElement(currentList); + + return false; // return false because we already iterated over the children using the custom + // iteration above +} + +bool QQmlDomAstCreator::visit(AST::Elision *list) +{ + if (!m_enableScriptExpressions) + return false; + + auto currentList = makeScriptList(list); + + for (auto it = list; it; it = it->next) { + auto current = makeGenericScriptElement(it->commaToken, DomType::ScriptElision); + currentList.append(ScriptElementVariant::fromElement(current)); + } + pushScriptElement(currentList); + + return false; // return false because we already iterated over the children using the custom + // iteration above +} + +bool QQmlDomAstCreator::visit(AST::PatternElement *) +{ + if (!m_enableScriptExpressions) + return false; + + return true; +} + +/*! + \internal + Avoid code-duplication, reuse this code when doing endVisit on types inheriting from + AST::PatternElement. +*/ +void QQmlDomAstCreator::endVisitHelper( + AST::PatternElement *pe, + const std::shared_ptr<ScriptElements::GenericScriptElement> ¤t) +{ + if (pe->equalToken.isValid()) + current->addLocation(FileLocationRegion::EqualTokenRegion, pe->equalToken); + + if (pe->identifierToken.isValid() && !pe->bindingIdentifier.isEmpty()) { + auto identifier = + std::make_shared<ScriptElements::IdentifierExpression>(pe->identifierToken); + identifier->setName(pe->bindingIdentifier); + current->insertChild(Fields::identifier, ScriptElementVariant::fromElement(identifier)); + } + if (pe->initializer) { + Q_SCRIPTELEMENT_EXIT_IF(scriptNodeStack.isEmpty() || scriptNodeStack.last().isList()); + current->insertChild(Fields::initializer, scriptNodeStack.last().takeVariant()); + scriptNodeStack.removeLast(); + } + if (pe->typeAnnotation) { + Q_SCRIPTELEMENT_EXIT_IF(scriptNodeStack.isEmpty() || scriptNodeStack.last().isList()); + current->insertChild(Fields::type, scriptNodeStack.last().takeVariant()); + scriptNodeStack.removeLast(); + } + if (pe->bindingTarget) { + Q_SCRIPTELEMENT_EXIT_IF(scriptNodeStack.isEmpty() || scriptNodeStack.last().isList()); + current->insertChild(Fields::bindingElement, scriptNodeStack.last().takeVariant()); + scriptNodeStack.removeLast(); + } +} + +void QQmlDomAstCreator::endVisit(AST::PatternElement *pe) +{ + if (!m_enableScriptExpressions) + return; + + auto element = makeGenericScriptElement(pe, DomType::ScriptPattern); + endVisitHelper(pe, element); + // check if helper disabled scriptexpressions + if (!m_enableScriptExpressions) + return; + + pushScriptElement(element); +} + +bool QQmlDomAstCreator::visit(AST::IfStatement *) +{ + if (!m_enableScriptExpressions) + return false; + + return true; +} + +void QQmlDomAstCreator::endVisit(AST::IfStatement *ifStatement) +{ + if (!m_enableScriptExpressions) + return; + + auto current = makeScriptElement<ScriptElements::IfStatement>(ifStatement); + current->addLocation(LeftParenthesisRegion, ifStatement->lparenToken); + current->addLocation(RightParenthesisRegion, ifStatement->rparenToken); + current->addLocation(ElseKeywordRegion, ifStatement->elseToken); + current->addLocation(IfKeywordRegion, ifStatement->ifToken); + + if (ifStatement->ko) { + Q_SCRIPTELEMENT_EXIT_IF(scriptNodeStack.isEmpty() || scriptNodeStack.last().isList()); + current->setAlternative(scriptNodeStack.last().takeVariant()); + scriptNodeStack.removeLast(); + } + + if (ifStatement->ok) { + Q_SCRIPTELEMENT_EXIT_IF(scriptNodeStack.isEmpty() || scriptNodeStack.last().isList()); + current->setConsequence(scriptNodeStack.last().takeVariant()); + scriptNodeStack.removeLast(); + } + if (ifStatement->expression) { + Q_SCRIPTELEMENT_EXIT_IF(scriptNodeStack.isEmpty() || scriptNodeStack.last().isList()); + current->setCondition(scriptNodeStack.last().takeVariant()); + scriptNodeStack.removeLast(); + } + + pushScriptElement(current); +} + +bool QQmlDomAstCreator::visit(AST::ReturnStatement *) +{ + if (!m_enableScriptExpressions) + return false; + + return true; +} + +void QQmlDomAstCreator::endVisit(AST::ReturnStatement *returnStatement) +{ + if (!m_enableScriptExpressions) + return; + + auto current = makeScriptElement<ScriptElements::ReturnStatement>(returnStatement); + current->addLocation(ReturnKeywordRegion, returnStatement->returnToken); + + if (returnStatement->expression) { + Q_SCRIPTELEMENT_EXIT_IF(scriptNodeStack.isEmpty() || scriptNodeStack.last().isList()); + current->setExpression(currentScriptNodeEl().takeVariant()); + removeCurrentScriptNode({}); + } + + pushScriptElement(current); +} + +bool QQmlDomAstCreator::visit(AST::FieldMemberExpression *) +{ + if (!m_enableScriptExpressions) + return false; + + return true; +} + +void QQmlDomAstCreator::endVisit(AST::FieldMemberExpression *expression) +{ + if (!m_enableScriptExpressions) + return; + + auto current = makeScriptElement<ScriptElements::BinaryExpression>(expression); + current->setOp(ScriptElements::BinaryExpression::FieldMemberAccess); + current->addLocation(FileLocationRegion::OperatorTokenRegion, expression->dotToken); + + if (expression->base) { + Q_SCRIPTELEMENT_EXIT_IF(scriptNodeStack.isEmpty() || scriptNodeStack.last().isList()); + current->setLeft(currentScriptNodeEl().takeVariant()); + removeCurrentScriptNode({}); + } + + auto scriptIdentifier = + std::make_shared<ScriptElements::IdentifierExpression>(expression->identifierToken); + scriptIdentifier->setName(expression->name); + current->setRight(ScriptElementVariant::fromElement(scriptIdentifier)); + + pushScriptElement(current); +} + +bool QQmlDomAstCreator::visit(AST::ArrayMemberExpression *) +{ + if (!m_enableScriptExpressions) + return false; + + return true; +} + +void QQmlDomAstCreator::endVisit(AST::ArrayMemberExpression *expression) +{ + if (!m_enableScriptExpressions) + return; + + auto current = makeScriptElement<ScriptElements::BinaryExpression>(expression); + current->setOp(ScriptElements::BinaryExpression::ArrayMemberAccess); + current->addLocation(FileLocationRegion::OperatorTokenRegion, expression->lbracketToken); + + if (expression->expression) { + Q_SCRIPTELEMENT_EXIT_IF(scriptNodeStack.isEmpty() || scriptNodeStack.last().isList()); + // if scriptNodeStack.last() is fieldmember expression, add expression to it instead of + // creating new one + current->setRight(currentScriptNodeEl().takeVariant()); + removeCurrentScriptNode({}); + } + + if (expression->base) { + Q_SCRIPTELEMENT_EXIT_IF(scriptNodeStack.isEmpty() || scriptNodeStack.last().isList()); + current->setLeft(currentScriptNodeEl().takeVariant()); + removeCurrentScriptNode({}); + } + + pushScriptElement(current); +} + +bool QQmlDomAstCreator::visit(AST::CallExpression *) +{ + if (!m_enableScriptExpressions) + return false; + + return true; +} + +void QQmlDomAstCreator::endVisit(AST::CallExpression *exp) +{ + if (!m_enableScriptExpressions) + return; + + auto current = makeGenericScriptElement(exp, DomType::ScriptCallExpression); + current->addLocation(LeftParenthesisRegion, exp->lparenToken); + current->addLocation(RightParenthesisRegion, exp->rparenToken); + + if (exp->arguments) { + Q_SCRIPTELEMENT_EXIT_IF(scriptNodeStack.isEmpty()); + current->insertChild(Fields::arguments, currentScriptNodeEl().takeList()); + removeCurrentScriptNode({}); + } else { + // insert empty list + current->insertChild(Fields::arguments, + ScriptElements::ScriptList(exp->lparenToken, exp->rparenToken)); + } + + if (exp->base) { + Q_SCRIPTELEMENT_EXIT_IF(scriptNodeStack.isEmpty() || scriptNodeStack.last().isList()); + current->insertChild(Fields::callee, currentScriptNodeEl().takeVariant()); + removeCurrentScriptNode({}); + } + + pushScriptElement(current); +} + +bool QQmlDomAstCreator::visit(AST::ArrayPattern *) +{ + if (!m_enableScriptExpressions) + return false; + + return true; +} + +void QQmlDomAstCreator::endVisit(AST::ArrayPattern *exp) +{ + if (!m_enableScriptExpressions) + return; + + auto current = makeGenericScriptElement(exp, DomType::ScriptArray); + + if (exp->elements) { + Q_SCRIPTELEMENT_EXIT_IF(scriptNodeStack.isEmpty()); + ScriptElements::ScriptList list = currentScriptNodeEl().takeList(); + list.replaceKindForGenericChildren(DomType::ScriptPattern, DomType::ScriptArrayEntry); + current->insertChild(Fields::elements, std::move(list)); + + removeCurrentScriptNode({}); + } else { + // insert empty list + current->insertChild(Fields::elements, + ScriptElements::ScriptList(exp->lbracketToken, exp->rbracketToken)); + } + + pushScriptElement(current); +} + +bool QQmlDomAstCreator::visit(AST::ObjectPattern *) +{ + if (!m_enableScriptExpressions) + return false; + + return true; +} + +void QQmlDomAstCreator::endVisit(AST::ObjectPattern *exp) +{ + if (!m_enableScriptExpressions) + return; + + auto current = makeGenericScriptElement(exp, DomType::ScriptObject); + + if (exp->properties) { + Q_SCRIPTELEMENT_EXIT_IF(scriptNodeStack.isEmpty()); + current->insertChild(Fields::properties, currentScriptNodeEl().takeList()); + removeCurrentScriptNode({}); + } else { + // insert empty list + current->insertChild(Fields::properties, + ScriptElements::ScriptList(exp->lbraceToken, exp->rbraceToken)); + } + + pushScriptElement(current); +} + +bool QQmlDomAstCreator::visit(AST::PatternProperty *) +{ + if (!m_enableScriptExpressions) + return false; + + return true; +} + +void QQmlDomAstCreator::endVisit(AST::PatternProperty *exp) +{ + if (!m_enableScriptExpressions) + return; + + auto current = makeGenericScriptElement(exp, DomType::ScriptProperty); + + // handle the stuff from PatternProperty's base class PatternElement + endVisitHelper(static_cast<PatternElement *>(exp), current); + + // check if helper disabled scriptexpressions + if (!m_enableScriptExpressions) + return; + + if (exp->name) { + Q_SCRIPTELEMENT_EXIT_IF(scriptNodeStack.isEmpty() || scriptNodeStack.last().isList()); + current->insertChild(Fields::name, currentScriptNodeEl().takeVariant()); + removeCurrentScriptNode({}); + } + + pushScriptElement(current); +} + +bool QQmlDomAstCreator::visit(AST::VariableStatement *) +{ + if (!m_enableScriptExpressions) + return false; + + return true; +} + +void QQmlDomAstCreator::endVisit(AST::VariableStatement *statement) +{ + if (!m_enableScriptExpressions) + return; + + auto current = makeGenericScriptElement(statement, DomType::ScriptVariableDeclaration); + current->addLocation(FileLocationRegion::TypeIdentifierRegion, statement->declarationKindToken); + + if (statement->declarations) { + Q_SCRIPTELEMENT_EXIT_IF(scriptNodeStack.isEmpty()); + + ScriptElements::ScriptList list = currentScriptNodeEl().takeList(); + list.replaceKindForGenericChildren(DomType::ScriptPattern, + DomType::ScriptVariableDeclarationEntry); + current->insertChild(Fields::declarations, std::move(list)); + + removeCurrentScriptNode({}); + } + + pushScriptElement(current); +} + +bool QQmlDomAstCreator::visit(AST::Type *) +{ + if (!m_enableScriptExpressions) + return false; + + return true; +} + +void QQmlDomAstCreator::endVisit(AST::Type *exp) +{ + if (!m_enableScriptExpressions) + return; + + auto current = makeGenericScriptElement(exp, DomType::ScriptType); + + if (exp->typeArgument) { + current->insertChild(Fields::typeArgumentName, + fieldMemberExpressionForQualifiedId(exp->typeArgument)); + current->addLocation(FileLocationRegion::IdentifierRegion, combineLocations(exp->typeArgument)); + } + + if (exp->typeId) { + current->insertChild(Fields::typeName, fieldMemberExpressionForQualifiedId(exp->typeId)); + current->addLocation(FileLocationRegion::TypeIdentifierRegion, combineLocations(exp->typeId)); + } + + pushScriptElement(current); +} + +bool QQmlDomAstCreator::visit(AST::DefaultClause *) +{ + if (!m_enableScriptExpressions) + return false; + + return true; +} + +void QQmlDomAstCreator::endVisit(AST::DefaultClause *exp) +{ + if (!m_enableScriptExpressions) + return; + + auto current = makeGenericScriptElement(exp, DomType::ScriptDefaultClause); + current->addLocation(DefaultKeywordRegion, exp->defaultToken); + current->addLocation(ColonTokenRegion, exp->colonToken); + + if (exp->statements) { + Q_SCRIPTELEMENT_EXIT_IF(scriptNodeStack.isEmpty()); + current->insertChild(Fields::statements, currentScriptNodeEl().takeList()); + removeCurrentScriptNode({}); + } + + pushScriptElement(current); +} + +bool QQmlDomAstCreator::visit(AST::CaseClause *) +{ + if (!m_enableScriptExpressions) + return false; + + return true; +} + +void QQmlDomAstCreator::endVisit(AST::CaseClause *exp) +{ + if (!m_enableScriptExpressions) + return; + + auto current = makeGenericScriptElement(exp, DomType::ScriptCaseClause); + current->addLocation(FileLocationRegion::CaseKeywordRegion, exp->caseToken); + current->addLocation(FileLocationRegion::ColonTokenRegion, exp->colonToken); + + if (exp->statements) { + Q_SCRIPTELEMENT_EXIT_IF(scriptNodeStack.isEmpty()); + current->insertChild(Fields::statements, currentScriptNodeEl().takeList()); + removeCurrentScriptNode({}); + } + + if (exp->expression) { + Q_SCRIPTELEMENT_EXIT_IF(scriptNodeStack.isEmpty() || scriptNodeStack.last().isList()); + current->insertChild(Fields::expression, currentScriptNodeEl().takeVariant()); + removeCurrentScriptNode({}); + } + + pushScriptElement(current); +} + +bool QQmlDomAstCreator::visit(AST::CaseClauses *) +{ + if (!m_enableScriptExpressions) + return false; + + return true; +} + +void QQmlDomAstCreator::endVisit(AST::CaseClauses *list) +{ + if (!m_enableScriptExpressions) + return; + + auto current = makeScriptList(list); + + for (auto it = list; it; it = it->next) { + Q_SCRIPTELEMENT_EXIT_IF(scriptNodeStack.isEmpty() || scriptNodeStack.last().isList()); + current.append(scriptNodeStack.takeLast().takeVariant()); + } + + current.reverse(); + pushScriptElement(current); +} + +bool QQmlDomAstCreator::visit(AST::CaseBlock *) +{ + if (!m_enableScriptExpressions) + return false; + + return true; +} + +void QQmlDomAstCreator::endVisit(AST::CaseBlock *exp) +{ + if (!m_enableScriptExpressions) + return; + + auto current = makeGenericScriptElement(exp, DomType::ScriptCaseBlock); + current->addLocation(FileLocationRegion::LeftBraceRegion, exp->lbraceToken); + current->addLocation(FileLocationRegion::RightBraceRegion, exp->rbraceToken); + + if (exp->moreClauses) { + Q_SCRIPTELEMENT_EXIT_IF(scriptNodeStack.isEmpty()); + current->insertChild(Fields::moreCaseClauses, currentScriptNodeEl().takeList()); + removeCurrentScriptNode({}); + } + + if (exp->defaultClause) { + Q_SCRIPTELEMENT_EXIT_IF(scriptNodeStack.isEmpty() || scriptNodeStack.last().isList()); + current->insertChild(Fields::defaultClause, currentScriptNodeEl().takeVariant()); + removeCurrentScriptNode({}); + } + + if (exp->clauses) { + Q_SCRIPTELEMENT_EXIT_IF(scriptNodeStack.isEmpty()); + current->insertChild(Fields::caseClauses, currentScriptNodeEl().takeList()); + removeCurrentScriptNode({}); + } + pushScriptElement(current); +} + +bool QQmlDomAstCreator::visit(AST::SwitchStatement *) +{ + if (!m_enableScriptExpressions) + return false; + + return true; +} + +void QQmlDomAstCreator::endVisit(AST::SwitchStatement *exp) +{ + if (!m_enableScriptExpressions) + return; + + auto current = makeGenericScriptElement(exp, DomType::ScriptSwitchStatement); + current->addLocation(FileLocationRegion::SwitchKeywordRegion, exp->switchToken); + current->addLocation(FileLocationRegion::LeftParenthesisRegion, exp->lparenToken); + current->addLocation(FileLocationRegion::RightParenthesisRegion, exp->rparenToken); + + if (exp->block) { + Q_SCRIPTELEMENT_EXIT_IF(scriptNodeStack.isEmpty() || scriptNodeStack.last().isList()); + current->insertChild(Fields::caseBlock, currentScriptNodeEl().takeVariant()); + removeCurrentScriptNode({}); + } + if (exp->expression) { + Q_SCRIPTELEMENT_EXIT_IF(scriptNodeStack.isEmpty() || scriptNodeStack.last().isList()); + current->insertChild(Fields::expression, currentScriptNodeEl().takeVariant()); + removeCurrentScriptNode({}); + } + + pushScriptElement(current); +} + +bool QQmlDomAstCreator::visit(AST::WhileStatement *) +{ + if (!m_enableScriptExpressions) + return false; + + return true; +} + +void QQmlDomAstCreator::endVisit(AST::WhileStatement *exp) +{ + if (!m_enableScriptExpressions) + return; + + auto current = makeGenericScriptElement(exp, DomType::ScriptWhileStatement); + current->addLocation(FileLocationRegion::WhileKeywordRegion, exp->whileToken); + current->addLocation(FileLocationRegion::LeftParenthesisRegion, exp->lparenToken); + current->addLocation(FileLocationRegion::RightParenthesisRegion, exp->rparenToken); + + if (exp->statement) { + Q_SCRIPTELEMENT_EXIT_IF(scriptNodeStack.isEmpty() || scriptNodeStack.last().isList()); + current->insertChild(Fields::body, currentScriptNodeEl().takeVariant()); + removeCurrentScriptNode({}); + } + + if (exp->expression) { + Q_SCRIPTELEMENT_EXIT_IF(scriptNodeStack.isEmpty() || scriptNodeStack.last().isList()); + current->insertChild(Fields::expression, currentScriptNodeEl().takeVariant()); + removeCurrentScriptNode({}); + } + + pushScriptElement(current); +} + +bool QQmlDomAstCreator::visit(AST::DoWhileStatement *) +{ + if (!m_enableScriptExpressions) + return false; + + return true; +} + +void QQmlDomAstCreator::endVisit(AST::DoWhileStatement *exp) +{ + if (!m_enableScriptExpressions) + return; + + auto current = makeGenericScriptElement(exp, DomType::ScriptDoWhileStatement); + current->addLocation(FileLocationRegion::DoKeywordRegion, exp->doToken); + current->addLocation(FileLocationRegion::WhileKeywordRegion, exp->whileToken); + current->addLocation(FileLocationRegion::LeftParenthesisRegion, exp->lparenToken); + current->addLocation(FileLocationRegion::RightParenthesisRegion, exp->rparenToken); + + if (exp->expression) { + Q_SCRIPTELEMENT_EXIT_IF(scriptNodeStack.isEmpty() || scriptNodeStack.last().isList()); + current->insertChild(Fields::expression, currentScriptNodeEl().takeVariant()); + removeCurrentScriptNode({}); + } + + if (exp->statement) { + Q_SCRIPTELEMENT_EXIT_IF(scriptNodeStack.isEmpty() || scriptNodeStack.last().isList()); + current->insertChild(Fields::body, currentScriptNodeEl().takeVariant()); + removeCurrentScriptNode({}); + } + + pushScriptElement(current); +} + +bool QQmlDomAstCreator::visit(AST::ForEachStatement *) +{ + if (!m_enableScriptExpressions) + return false; + + return true; +} + +void QQmlDomAstCreator::endVisit(AST::ForEachStatement *exp) +{ + if (!m_enableScriptExpressions) + return; + + auto current = makeGenericScriptElement(exp, DomType::ScriptForEachStatement); + current->addLocation(FileLocationRegion::ForKeywordRegion, exp->forToken); + current->addLocation(FileLocationRegion::InOfTokenRegion, exp->inOfToken); + current->addLocation(FileLocationRegion::LeftParenthesisRegion, exp->lparenToken); + current->addLocation(FileLocationRegion::RightParenthesisRegion, exp->rparenToken); + + if (exp->statement) { + Q_SCRIPTELEMENT_EXIT_IF(scriptNodeStack.isEmpty() || scriptNodeStack.last().isList()); + current->insertChild(Fields::body, currentScriptNodeEl().takeVariant()); + removeCurrentScriptNode({}); + } + if (exp->expression) { + Q_SCRIPTELEMENT_EXIT_IF(scriptNodeStack.isEmpty() || scriptNodeStack.last().isList()); + current->insertChild(Fields::expression, currentScriptNodeEl().takeVariant()); + removeCurrentScriptNode({}); + } + + if (exp->lhs) { + Q_SCRIPTELEMENT_EXIT_IF(scriptNodeStack.isEmpty() || scriptNodeStack.last().isList()); + current->insertChild(Fields::bindingElement, currentScriptNodeEl().takeVariant()); + removeCurrentScriptNode({}); + + if (auto pe = AST::cast<PatternElement *>(exp->lhs); + pe && pe->declarationKindToken.isValid()) { + current->addLocation(FileLocationRegion::TypeIdentifierRegion, + pe->declarationKindToken); + } + } + + pushScriptElement(current); +} + + +bool QQmlDomAstCreator::visit(AST::ClassExpression *) +{ + // TODO: Add support for js expressions in classes + // For now, turning off explicitly to avoid unwanted problems + if (m_enableScriptExpressions) + Q_SCRIPTELEMENT_DISABLE(); + return true; +} + +void QQmlDomAstCreator::endVisit(AST::ClassExpression *) +{ +} + +bool QQmlDomAstCreator::visit(AST::TemplateLiteral *) +{ + // TODO: Add support for template literals + // For now, turning off explicitly to avoid unwanted problems + if (m_enableScriptExpressions) + Q_SCRIPTELEMENT_DISABLE(); + return true; +} + +bool QQmlDomAstCreator::visit(AST::TryStatement *) +{ + return m_enableScriptExpressions; +} + +void QQmlDomAstCreator::endVisit(AST::TryStatement *statement) +{ + if (!m_enableScriptExpressions) + return; + + auto current = makeGenericScriptElement(statement, DomType::ScriptTryCatchStatement); + current->addLocation(FileLocationRegion::TryKeywordRegion, statement->tryToken); + + if (auto exp = statement->finallyExpression) { + current->addLocation(FileLocationRegion::FinallyKeywordRegion, exp->finallyToken); + + Q_SCRIPTELEMENT_EXIT_IF(scriptNodeStack.isEmpty() || scriptNodeStack.last().isList()); + current->insertChild(Fields::finallyBlock, currentScriptNodeEl().takeVariant()); + removeCurrentScriptNode({}); + } + + if (auto exp = statement->catchExpression) { + current->addLocation(FileLocationRegion::CatchKeywordRegion, exp->catchToken); + current->addLocation(FileLocationRegion::LeftParenthesisRegion, exp->lparenToken); + current->addLocation(FileLocationRegion::RightParenthesisRegion, exp->rparenToken); + + Q_SCRIPTELEMENT_EXIT_IF(scriptNodeStack.isEmpty() || scriptNodeStack.last().isList()); + current->insertChild(Fields::catchBlock, currentScriptNodeEl().takeVariant()); + removeCurrentScriptNode({}); + Q_SCRIPTELEMENT_EXIT_IF(scriptNodeStack.isEmpty() || scriptNodeStack.last().isList()); + current->insertChild(Fields::catchParameter, currentScriptNodeEl().takeVariant()); + removeCurrentScriptNode({}); + } + + if (statement->statement) { + Q_SCRIPTELEMENT_EXIT_IF(scriptNodeStack.isEmpty() || scriptNodeStack.last().isList()); + current->insertChild(Fields::block, currentScriptNodeEl().takeVariant()); + removeCurrentScriptNode({}); + } + + pushScriptElement(current); +} + +bool QQmlDomAstCreator::visit(AST::Catch *) +{ + // handled in visit(AST::TryStatement* ) + return m_enableScriptExpressions; +} + +void QQmlDomAstCreator::endVisit(AST::Catch *) +{ + // handled in endVisit(AST::TryStatement* ) +} + +bool QQmlDomAstCreator::visit(AST::Finally *) +{ + // handled in visit(AST::TryStatement* ) + return m_enableScriptExpressions; +} + +void QQmlDomAstCreator::endVisit(AST::Finally *) +{ + // handled in endVisit(AST::TryStatement* ) +} + +bool QQmlDomAstCreator::visit(AST::ThrowStatement *) +{ + return m_enableScriptExpressions; +} + +void QQmlDomAstCreator::endVisit(AST::ThrowStatement *statement) +{ + if (!m_enableScriptExpressions) + return; + + auto current = makeGenericScriptElement(statement, DomType::ScriptThrowStatement); + current->addLocation(FileLocationRegion::ThrowKeywordRegion, statement->throwToken); + + if (statement->expression) { + Q_SCRIPTELEMENT_EXIT_IF(scriptNodeStack.isEmpty() || scriptNodeStack.last().isList()); + current->insertChild(Fields::expression, currentScriptNodeEl().takeVariant()); + removeCurrentScriptNode({}); + } + + pushScriptElement(current); +} + +bool QQmlDomAstCreator::visit(AST::LabelledStatement *) +{ + return m_enableScriptExpressions; +} + +void QQmlDomAstCreator::endVisit(AST::LabelledStatement *statement) +{ + if (!m_enableScriptExpressions) + return; + + auto current = makeGenericScriptElement(statement, DomType::ScriptLabelledStatement); + current->addLocation(FileLocationRegion::ColonTokenRegion, statement->colonToken); + + auto label = std::make_shared<ScriptElements::IdentifierExpression>(statement->identifierToken); + label->setName(statement->label); + current->insertChild(Fields::label, ScriptElementVariant::fromElement(label)); + + + if (statement->statement) { + Q_SCRIPTELEMENT_EXIT_IF(scriptNodeStack.isEmpty() || scriptNodeStack.last().isList()); + current->insertChild(Fields::statement, currentScriptNodeEl().takeVariant()); + removeCurrentScriptNode({}); + } + + pushScriptElement(current); +} + +bool QQmlDomAstCreator::visit(AST::BreakStatement *) +{ + return m_enableScriptExpressions; +} + +void QQmlDomAstCreator::endVisit(AST::BreakStatement *statement) +{ + if (!m_enableScriptExpressions) + return; + + auto current = makeGenericScriptElement(statement, DomType::ScriptBreakStatement); + current->addLocation(FileLocationRegion::BreakKeywordRegion, statement->breakToken); + + if (!statement->label.isEmpty()) { + auto label = + std::make_shared<ScriptElements::IdentifierExpression>(statement->identifierToken); + label->setName(statement->label); + current->insertChild(Fields::label, ScriptElementVariant::fromElement(label)); + } + + pushScriptElement(current); +} + +// note: thats for comma expressions +bool QQmlDomAstCreator::visit(AST::Expression *) +{ + return m_enableScriptExpressions; +} + +// note: thats for comma expressions +void QQmlDomAstCreator::endVisit(AST::Expression *commaExpression) +{ + if (!m_enableScriptExpressions) + return; + + auto current = makeScriptElement<ScriptElements::BinaryExpression>(commaExpression); + current->addLocation(OperatorTokenRegion, commaExpression->commaToken); + + if (commaExpression->right) { + Q_SCRIPTELEMENT_EXIT_IF(scriptNodeStack.isEmpty() || scriptNodeStack.last().isList()); + current->setRight(currentScriptNodeEl().takeVariant()); + removeCurrentScriptNode({}); + } + + if (commaExpression->left) { + Q_SCRIPTELEMENT_EXIT_IF(scriptNodeStack.isEmpty() || scriptNodeStack.last().isList()); + current->setLeft(currentScriptNodeEl().takeVariant()); + removeCurrentScriptNode({}); + } + + pushScriptElement(current); +} + +bool QQmlDomAstCreator::visit(AST::ConditionalExpression *) +{ + return m_enableScriptExpressions; +} + +void QQmlDomAstCreator::endVisit(AST::ConditionalExpression *expression) +{ + if (!m_enableScriptExpressions) + return; + + auto current = makeGenericScriptElement(expression, DomType::ScriptConditionalExpression); + current->addLocation(FileLocationRegion::QuestionMarkTokenRegion, expression->questionToken); + current->addLocation(FileLocationRegion::ColonTokenRegion, expression->colonToken); + + if (expression->ko) { + Q_SCRIPTELEMENT_EXIT_IF(scriptNodeStack.isEmpty() || scriptNodeStack.last().isList()); + current->insertChild(Fields::alternative, currentScriptNodeEl().takeVariant()); + removeCurrentScriptNode({}); + } + + if (expression->ok) { + Q_SCRIPTELEMENT_EXIT_IF(scriptNodeStack.isEmpty() || scriptNodeStack.last().isList()); + current->insertChild(Fields::consequence, currentScriptNodeEl().takeVariant()); + removeCurrentScriptNode({}); + } + + if (expression->expression) { + Q_SCRIPTELEMENT_EXIT_IF(scriptNodeStack.isEmpty() || scriptNodeStack.last().isList()); + current->insertChild(Fields::condition, currentScriptNodeEl().takeVariant()); + removeCurrentScriptNode({}); + } + + pushScriptElement(current); +} + +bool QQmlDomAstCreator::visit(AST::ContinueStatement *) +{ + return m_enableScriptExpressions; +} + +void QQmlDomAstCreator::endVisit(AST::ContinueStatement *statement) +{ + if (!m_enableScriptExpressions) + return; + + auto current = makeGenericScriptElement(statement, DomType::ScriptContinueStatement); + current->addLocation(FileLocationRegion::ContinueKeywordRegion, statement->continueToken); + + if (!statement->label.isEmpty()) { + auto label = + std::make_shared<ScriptElements::IdentifierExpression>(statement->identifierToken); + label->setName(statement->label); + current->insertChild(Fields::label, ScriptElementVariant::fromElement(label)); + } + + pushScriptElement(current); +} + +/*! + \internal + Helper to create unary expressions from AST nodes. + \sa makeGenericScriptElement + */ +std::shared_ptr<ScriptElements::GenericScriptElement> +QQmlDomAstCreator::makeUnaryExpression(AST::Node *expression, QQmlJS::SourceLocation operatorToken, + bool hasExpression, UnaryExpressionKind kind) +{ + const DomType type = [&kind]() { + switch (kind) { + case Prefix: + return DomType::ScriptUnaryExpression; + case Postfix: + return DomType::ScriptPostExpression; + } + Q_UNREACHABLE_RETURN(DomType::ScriptUnaryExpression); + }(); + + auto current = makeGenericScriptElement(expression, type); + current->addLocation(FileLocationRegion::OperatorTokenRegion, operatorToken); + + if (hasExpression) { + if (scriptNodeStack.isEmpty() || scriptNodeStack.last().isList()) { + Q_SCRIPTELEMENT_DISABLE(); + return {}; + } + current->insertChild(Fields::expression, currentScriptNodeEl().takeVariant()); + removeCurrentScriptNode({}); + } + + return current; +} + +bool QQmlDomAstCreator::visit(AST::UnaryMinusExpression *) +{ + return m_enableScriptExpressions; +} + +void QQmlDomAstCreator::endVisit(AST::UnaryMinusExpression *statement) +{ + if (!m_enableScriptExpressions) + return; + + auto current = + makeUnaryExpression(statement, statement->minusToken, statement->expression, Prefix); + if (!current) + return; + + pushScriptElement(current); +} + +bool QQmlDomAstCreator::visit(AST::UnaryPlusExpression *) +{ + return m_enableScriptExpressions; +} + +void QQmlDomAstCreator::endVisit(AST::UnaryPlusExpression *statement) +{ + if (!m_enableScriptExpressions) + return; + + auto current = + makeUnaryExpression(statement, statement->plusToken, statement->expression, Prefix); + if (!current) + return; + + pushScriptElement(current); +} + +bool QQmlDomAstCreator::visit(AST::TildeExpression *) +{ + return m_enableScriptExpressions; +} + +void QQmlDomAstCreator::endVisit(AST::TildeExpression *statement) +{ + if (!m_enableScriptExpressions) + return; + + auto current = + makeUnaryExpression(statement, statement->tildeToken, statement->expression, Prefix); + if (!current) + return; + + pushScriptElement(current); +} + +bool QQmlDomAstCreator::visit(AST::NotExpression *) +{ + return m_enableScriptExpressions; +} + +void QQmlDomAstCreator::endVisit(AST::NotExpression *statement) +{ + if (!m_enableScriptExpressions) + return; + + auto current = + makeUnaryExpression(statement, statement->notToken, statement->expression, Prefix); + if (!current) + return; + + pushScriptElement(current); +} + +bool QQmlDomAstCreator::visit(AST::TypeOfExpression *) +{ + return m_enableScriptExpressions; +} + +void QQmlDomAstCreator::endVisit(AST::TypeOfExpression *statement) +{ + if (!m_enableScriptExpressions) + return; + + auto current = + makeUnaryExpression(statement, statement->typeofToken, statement->expression, Prefix); + if (!current) + return; + + pushScriptElement(current); +} + +bool QQmlDomAstCreator::visit(AST::DeleteExpression *) +{ + return m_enableScriptExpressions; +} + +void QQmlDomAstCreator::endVisit(AST::DeleteExpression *statement) +{ + if (!m_enableScriptExpressions) + return; + + auto current = + makeUnaryExpression(statement, statement->deleteToken, statement->expression, Prefix); + if (!current) + return; + + pushScriptElement(current); +} + +bool QQmlDomAstCreator::visit(AST::VoidExpression *) +{ + return m_enableScriptExpressions; +} + +void QQmlDomAstCreator::endVisit(AST::VoidExpression *statement) +{ + if (!m_enableScriptExpressions) + return; + + auto current = + makeUnaryExpression(statement, statement->voidToken, statement->expression, Prefix); + if (!current) + return; + + pushScriptElement(current); +} + +bool QQmlDomAstCreator::visit(AST::PostDecrementExpression *) +{ + return m_enableScriptExpressions; +} + +void QQmlDomAstCreator::endVisit(AST::PostDecrementExpression *statement) +{ + if (!m_enableScriptExpressions) + return; + + auto current = + makeUnaryExpression(statement, statement->decrementToken, statement->base, Postfix); + if (!current) + return; + + pushScriptElement(current); +} + +bool QQmlDomAstCreator::visit(AST::PostIncrementExpression *) +{ + return m_enableScriptExpressions; +} + +void QQmlDomAstCreator::endVisit(AST::PostIncrementExpression *statement) +{ + if (!m_enableScriptExpressions) + return; + + auto current = + makeUnaryExpression(statement, statement->incrementToken, statement->base, Postfix); + if (!current) + return; + + pushScriptElement(current); +} + +bool QQmlDomAstCreator::visit(AST::PreIncrementExpression *) +{ + return m_enableScriptExpressions; +} + +void QQmlDomAstCreator::endVisit(AST::PreIncrementExpression *statement) +{ + if (!m_enableScriptExpressions) + return; + + auto current = makeUnaryExpression(statement, statement->incrementToken, statement->expression, + Prefix); + if (!current) + return; + + pushScriptElement(current); +} + +bool QQmlDomAstCreator::visit(AST::EmptyStatement *) +{ + return m_enableScriptExpressions; +} + +void QQmlDomAstCreator::endVisit(AST::EmptyStatement *statement) +{ + if (!m_enableScriptExpressions) + return; + + auto current = makeGenericScriptElement(statement, DomType::ScriptEmptyStatement); + current->addLocation(FileLocationRegion::SemicolonTokenRegion, statement->semicolonToken); + pushScriptElement(current); +} + +bool QQmlDomAstCreator::visit(AST::NestedExpression *) +{ + return m_enableScriptExpressions; +} + +void QQmlDomAstCreator::endVisit(AST::NestedExpression *expression) +{ + if (!m_enableScriptExpressions) + return; + + auto current = makeGenericScriptElement(expression, DomType::ScriptParenthesizedExpression); + current->addLocation(FileLocationRegion::LeftParenthesisRegion, expression->lparenToken); + current->addLocation(FileLocationRegion::RightParenthesisRegion, expression->rparenToken); + + if (expression->expression) { + Q_SCRIPTELEMENT_EXIT_IF(scriptNodeStack.isEmpty() || scriptNodeStack.last().isList()); + current->insertChild(Fields::expression, currentScriptNodeEl().takeVariant()); + removeCurrentScriptNode({}); + } + + pushScriptElement(current); +} + +bool QQmlDomAstCreator::visit(AST::PreDecrementExpression *) +{ + return m_enableScriptExpressions; +} + +void QQmlDomAstCreator::endVisit(AST::PreDecrementExpression *statement) +{ + if (!m_enableScriptExpressions) + return; + + auto current = makeUnaryExpression(statement, statement->decrementToken, statement->expression, + Prefix); + if (!current) + return; + + pushScriptElement(current); +} + +static const DomEnvironment *environmentFrom(MutableDomItem &qmlFile) +{ + auto top = qmlFile.top(); + if (!top) { + return {}; + } + auto domEnvironment = top.as<DomEnvironment>(); + if (!domEnvironment) { + return {}; + } + return domEnvironment; +} + +static QStringList qmldirFilesFrom(MutableDomItem &qmlFile) +{ + if (auto env = environmentFrom(qmlFile)) + return env->qmldirFiles(); + + return {}; +} + +QQmlDomAstCreatorWithQQmlJSScope::QQmlDomAstCreatorWithQQmlJSScope(const QQmlJSScope::Ptr ¤t, + MutableDomItem &qmlFile, + QQmlJSLogger *logger, + QQmlJSImporter *importer) + : m_root(current), + m_logger(logger), + m_importer(importer), + m_implicitImportDirectory(QQmlJSImportVisitor::implicitImportDirectory( + m_logger->fileName(), m_importer->resourceFileMapper())), + m_scopeCreator(m_root, m_importer, m_logger, m_implicitImportDirectory, + qmldirFilesFrom(qmlFile)), + m_domCreator(qmlFile) +{ +} + +#define X(name) \ + bool QQmlDomAstCreatorWithQQmlJSScope::visit(name *node) \ + { \ + return visitT(node); \ + } \ + void QQmlDomAstCreatorWithQQmlJSScope::endVisit(name *node) \ + { \ + endVisitT(node); \ + } +QQmlJSASTClassListToVisit +#undef X + +void QQmlDomAstCreatorWithQQmlJSScope::setScopeInDomAfterEndvisit() +{ + const QQmlJSScope::ConstPtr scope = m_scopeCreator.m_currentScope; + if (!m_domCreator.scriptNodeStack.isEmpty()) { + auto topOfStack = m_domCreator.currentScriptNodeEl(); + switch (topOfStack.kind) { + case DomType::ScriptBlockStatement: + case DomType::ScriptForStatement: + case DomType::ScriptForEachStatement: + case DomType::ScriptDoWhileStatement: + case DomType::ScriptWhileStatement: + case DomType::List: + m_domCreator.currentScriptNodeEl().setSemanticScope(scope); + break; + // TODO: find which script elements also have a scope and implement them here + default: + break; + }; + } else if (!m_domCreator.nodeStack.isEmpty()) { + std::visit( + [&scope](auto &&e) { + using U = std::remove_cv_t<std::remove_reference_t<decltype(e)>>; + // TODO: find which dom elements also have a scope and implement them here + if constexpr (std::is_same_v<U, QmlObject>) { + e.setSemanticScope(scope); + } else if constexpr (std::is_same_v<U, QmlComponent>) { + e.setSemanticScope(scope); + } else if constexpr (std::is_same_v<U, MethodInfo>) { + if (e.body) { + if (auto scriptElement = e.body->scriptElement()) { + scriptElement.base()->setSemanticScope(scope); + } + } + e.setSemanticScope(scope); + } + }, + m_domCreator.currentNodeEl().item.value); + } +} + +void QQmlDomAstCreatorWithQQmlJSScope::setScopeInDomBeforeEndvisit() +{ + const QQmlJSScope::ConstPtr scope = m_scopeCreator.m_currentScope; + + // depending whether the property definition has a binding, the property definition might be + // either at the last position in the stack or at the position before the last position. + if (m_domCreator.nodeStack.size() > 1 + && m_domCreator.nodeStack.last().item.kind == DomType::Binding) { + std::visit( + [&scope](auto &&e) { + using U = std::remove_cv_t<std::remove_reference_t<decltype(e)>>; + if constexpr (std::is_same_v<U, PropertyDefinition>) { + // Make sure to use the property definition scope instead of the binding + // scope. If the current scope is a binding scope (this happens when the + // property definition has a binding, like `property int i: 45` for + // example), then the property definition scope is the parent of the current + // scope. + e.setSemanticScope(scope->scopeType() == QQmlSA::ScopeType::JSFunctionScope + ? scope->parentScope() + : scope); + Q_ASSERT(e.semanticScope() + && e.semanticScope()->scopeType() == QQmlSA::ScopeType::QMLScope); + } + }, + m_domCreator.currentNodeEl(1).item.value); + } + if (m_domCreator.nodeStack.size() > 0) { + std::visit( + [&scope](auto &&e) { + using U = std::remove_cv_t<std::remove_reference_t<decltype(e)>>; + if constexpr (std::is_same_v<U, PropertyDefinition>) { + e.setSemanticScope(scope); + Q_ASSERT(e.semanticScope()); + } else if constexpr (std::is_same_v<U, MethodInfo>) { + if (e.methodType == MethodInfo::Signal) { + e.setSemanticScope(scope); + } + } + }, + m_domCreator.currentNodeEl().item.value); + } +} + +void QQmlDomAstCreatorWithQQmlJSScope::throwRecursionDepthError() +{ +} + +} // end namespace Dom +} // end namespace QQmlJS + +#undef Q_SCRIPTELEMENT_DISABLE +#undef Q_SCRIPTELEMENT_EXIT_IF + +QT_END_NAMESPACE |