// 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
// Qt-Security score:significant

#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 "qqmldomastdumper_p.h"
#include "qqmldomastcreator_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>

Q_STATIC_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;
}

QQmlDomAstCreatorBase::QmlStackElement &QQmlDomAstCreatorBase::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();
}

QQmlDomAstCreatorBase::QmlStackElement &QQmlDomAstCreatorBase::currentNodeEl(int i)
{
    Q_ASSERT_X(i < nodeStack.size() && i >= 0, "currentNode", "Stack does not contain element!");
    return nodeStack[nodeStack.size() - i - 1];
}

QQmlDomAstCreatorBase::ScriptStackElement &QQmlDomAstCreatorBase::currentScriptNodeEl(int i)
{
    Q_ASSERT_X(i < scriptNodeStack.size() && i >= 0, "currentNode",
               "Stack does not contain element!");
    return scriptNodeStack[scriptNodeStack.size() - i - 1];
}

QQmlDomAstCreatorBase::DomValue &QQmlDomAstCreatorBase::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 QQmlDomAstCreatorBase::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 QQmlDomAstCreatorBase::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 &
QQmlDomAstCreatorBase::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:"
                        << FileLocations::canonicalPathForTesting(ownerFileLocations)
                                   .append(pathFromOwner.toString());
    e->updatePathFromOwner(pathFromOwner);
    e->createFileLocations(ownerFileLocations);
    return element;
}

FileLocations::Tree QQmlDomAstCreatorBase::createMap(const FileLocations::Tree &base, const Path &p, AST::Node *n)
{
    FileLocations::Tree res = FileLocations::ensure(base, p);
    if (n)
        FileLocations::addRegion(res, MainRegion, combineLocations(n));
    return res;
}

FileLocations::Tree QQmlDomAstCreatorBase::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);
}

QQmlDomAstCreatorBase::QQmlDomAstCreatorBase(const MutableDomItem &qmlFile)
    : qmlFile(qmlFile),
      qmlFilePtr(qmlFile.ownerAs<QmlFile>()),
      rootMap(qmlFilePtr->fileLocationsTree())
{
}

bool QQmlDomAstCreatorBase::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 currentFile = envPtr->domCreationOption() == Extended
                    ? QQmlJSUtils::qmlBuildPathFromSourcePath(
                              envPtr->semanticAnalysis().m_mapper.get(),
                              qmlFile.canonicalFilePath())
                    : qmlFile.canonicalFilePath();

            const QDir implicitImportDir = QFileInfo(currentFile).dir();
            const QString implicitImportDirPath = implicitImportDir.canonicalPath();
            envPtr->loadFile(FileToLoad::fromFileSystem(envPtr, implicitImportDirPath),
                             DomItem::Callback(), DomType::QmlDirectory);

            // also load the qmldir from the implicit directory, if existing
            if (implicitImportDir.exists(u"qmldir"_s)) {
                const QString implicitImportQmldir = implicitImportDirPath + u"/qmldir"_s;
                envPtr->loadFile(FileToLoad::fromFileSystem(envPtr, implicitImportQmldir),
                                 DomItem::Callback(), DomType::QmldirFile);
            }
        }
    }
    // 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 QQmlDomAstCreatorBase::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 QQmlDomAstCreatorBase::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().withField(Fields::values).withIndex(i), t);
        FileLocations::addRegion(subMap, PragmaValuesRegion, t->location);
        ++i;
    }

    return true;
}

bool QQmlDomAstCreatorBase::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 QQmlDomAstCreatorBase::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);

        const auto fileLocations = nodeStack.last().fileLocations;
        FileLocations::addRegion(fileLocations, SignalKeywordRegion, el->propertyToken());
        FileLocations::addRegion(fileLocations, IdentifierRegion, el->identifierToken);
        if (el->lparenToken.isValid())
            FileLocations::addRegion(fileLocations, LeftParenthesisRegion, el->lparenToken);
        if (el->rparenToken.isValid())
            FileLocations::addRegion(fileLocations, RightParenthesisRegion, el->rparenToken);

        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());
            if (!args->colonToken.isValid())
                param.typeAnnotationStyle = MethodParameter::TypeAnnotationStyle::Prefix;
            mInfo.parameters.append(param);
            auto argLocs = FileLocations::ensure(nodeStack.last().fileLocations,
                                                 Path::fromField(Fields::parameters).withIndex(idx));
            FileLocations::addRegion(argLocs, MainRegion, combineLocations(args));
            FileLocations::addRegion(argLocs, IdentifierRegion, args->identifierToken);
            if (args->type) {
                if (args->colonToken.isValid())
                    FileLocations::addRegion(argLocs, ColonTokenRegion, args->colonToken);
                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");
        p.isVirtual = el->isVirtual();
        p.isOverride = el->isOverride();
        p.isFinal = el->isFinal();
        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.withField(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 (el->typeModifierToken.isValid())
            FileLocations::addRegion(nodeStack.last().fileLocations, TypeModifierRegion, el->typeModifierToken);
        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.isVirtual) {
            FileLocations::addRegion(nodeStack.last().fileLocations, VirtualKeywordRegion,
                                     el->virtualToken());
        }
        if (p.isOverride) {
            FileLocations::addRegion(nodeStack.last().fileLocations, OverrideKeywordRegion,
                                     el->overrideToken());
        }
        if (p.isFinal) {
            FileLocations::addRegion(nodeStack.last().fileLocations, FinalKeywordRegion,
                                     el->finalToken());
        }
        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->statement);
            FileLocations::addRegion(bLoc, ColonTokenRegion, el->colonToken);
            FileLocations::Tree valueLoc = FileLocations::ensure(bLoc, Path::fromField(Fields::value));
            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({ std::move(bPathFromOwner), *bPtr, std::move(bLoc) });
        }
        break;
    }
    }
    return true;
}

void QQmlDomAstCreatorBase::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().withField(Fields::scriptElement),
                    FileLocations::ensure(currentNodeEl().fileLocations,
                                          Path().withField(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.withField(Fields::bindings)
                                              .withKey(pDef.name)
                                              .withIndex(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 = std::move(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 = std::move(m);
    } break;
    default:
        Q_UNREACHABLE();
    }
    removeCurrentNode({});
}

void QQmlDomAstCreatorBase::endVisit(AST::FormalParameterList *list)
{
    endVisitForLists(list);
}

bool QQmlDomAstCreatorBase::visit(AST::FunctionExpression *)
{
    ++m_nestedFunctionDepth;
    if (!m_enableScriptExpressions)
        return false;

    return true;
}

ScriptElementVariant QQmlDomAstCreatorBase::prepareBodyForFunction(AST::FunctionExpression *fExpression)
{
    Q_ASSERT(!scriptNodeStack.isEmpty() || !fExpression->body);

    if (fExpression->body) {
        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(fExpression->lbraceToken, fExpression->rbraceToken));
            body->setStatements(currentScriptNodeEl().takeList());
            if (auto semanticScope = body->statements().semanticScope())
                body->setSemanticScope(semanticScope);
            auto result = ScriptElementVariant::fromElement(body);
            removeCurrentScriptNode({});
            return result;
        } else {
            auto result = currentScriptNodeEl().takeVariant();
            removeCurrentScriptNode({});
            return result;
        }
        Q_UNREACHABLE_RETURN({});
    }

    // for convenience purposes: insert an empty BlockStatement
    auto body = std::make_shared<ScriptElements::BlockStatement>(
            combineLocations(fExpression->lbraceToken, fExpression->rbraceToken));
    return ScriptElementVariant::fromElement(body);
}

void QQmlDomAstCreatorBase::endVisit(AST::FunctionExpression *fExpression)
{
    --m_nestedFunctionDepth;
    if (!m_enableScriptExpressions)
        return;

    auto current = makeGenericScriptElement(fExpression, DomType::ScriptFunctionExpression);
    if (fExpression->identifierToken.isValid())
        current->addLocation(IdentifierRegion, fExpression->identifierToken);
    if (fExpression->functionToken.isValid())
        current->addLocation(FunctionKeywordRegion, fExpression->functionToken);
    if (fExpression->starToken.isValid())
        current->addLocation(StarTokenRegion, fExpression->starToken);
    if (fExpression->lparenToken.isValid())
        current->addLocation(LeftParenthesisRegion, fExpression->lparenToken);
    if (fExpression->rparenToken.isValid())
        current->addLocation(RightParenthesisRegion, fExpression->rparenToken);
    if (fExpression->lbraceToken.isValid())
        current->addLocation(LeftBraceRegion, fExpression->lbraceToken);
    if (fExpression->rbraceToken.isValid())
        current->addLocation(RightBraceRegion, fExpression->rbraceToken);
    if (fExpression->typeAnnotation) {
        current->addLocation(TypeIdentifierRegion,
                             combineLocations(fExpression->typeAnnotation->type));
    }

    Q_SCRIPTELEMENT_EXIT_IF(scriptNodeStack.isEmpty() && fExpression->body);
    current->insertChild(Fields::body, prepareBodyForFunction(fExpression));

    if (fExpression->typeAnnotation) {
        Q_SCRIPTELEMENT_EXIT_IF(!stackHasScriptVariant());
        current->insertChild(Fields::returnType, currentScriptNodeEl().takeVariant());
        scriptNodeStack.removeLast();
    }
    if (fExpression->formals) {
        Q_SCRIPTELEMENT_EXIT_IF(!stackHasScriptList());
        current->insertChild(Fields::parameters, currentScriptNodeEl().takeList());
        scriptNodeStack.removeLast();
    }

    if (!fExpression->name.isEmpty())
        current->insertValue(Fields::name, fExpression->name);

    pushScriptElement(current);
}

bool QQmlDomAstCreatorBase::visit(AST::FunctionDeclaration *fDef)
{
    // Treat nested functions as (named) lambdas instead of Qml Object methods.
    if (m_nestedFunctionDepth > 0) {
        return visit(static_cast<FunctionExpression *>(fDef));
    }
    ++m_nestedFunctionDepth;
    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;
    m.isGenerator = fDef->isGenerator;

    SourceLocation bodyLoc = fDef->body ? combineLocations(fDef->body)
                                        : combineLocations(fDef->lbraceToken, fDef->rbraceToken);
    m.body = std::make_shared<ScriptExpression>(
            code.mid(bodyLoc.offset, bodyLoc.length), qmlFilePtr->engine(), fDef->body,
            qmlFilePtr->astComments(), ScriptExpression::ExpressionType::FunctionBody, bodyLoc);

    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);
    }

    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::fromField(Fields::body));
    FileLocations::addRegion(bodyTree, MainRegion, bodyLoc);
    if (fDef->functionToken.isValid())
        FileLocations::addRegion(fLoc, FunctionKeywordRegion, fDef->functionToken);
    if (fDef->starToken.isValid())
        FileLocations::addRegion(fLoc, StarTokenRegion, fDef->starToken);
    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);
            param.defaultValue = std::make_shared<ScriptExpression>(
                    code.mid(loc.offset, loc.length), qmlFilePtr->engine(),
                    args->element->initializer, qmlFilePtr->astComments(),
                    ScriptExpression::ExpressionType::ArgInitializer, loc);
        }
        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::fromField(Fields::parameters).withIndex(idx));
        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 QQmlDomAstCreatorBase::visit(AST::UiSourceElement *el)
{
    if (AST::cast<VariableStatement *>(el->sourceElement)) {
        qmlFile.addError(astParseErrors().warning(
                "JavaScript declarations are not allowed in QML elements"_L1));
        return false;
    }
    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 QQmlDomAstCreatorBase::endVisit(AST::FunctionDeclaration *fDef)
{
    // Treat nested functions as (named) lambdas instead of Qml Object methods.
    if (m_nestedFunctionDepth > 1) {
        endVisit(static_cast<FunctionExpression *>(fDef));
        return;
    }
    --m_nestedFunctionDepth;
    MethodInfo &m = std::get<MethodInfo>(currentNode().value);
    const FileLocations::Tree bodyTree =
            FileLocations::ensure(currentNodeEl().fileLocations, Path().withField(Fields::body));
    const Path bodyPath = Path().withField(Fields::scriptElement);

    if (!m_enableScriptExpressions)
        return;

    Q_SCRIPTELEMENT_EXIT_IF(scriptNodeStack.isEmpty() && fDef->body);
    m.body->setScriptElement(
            finalizeScriptExpression(prepareBodyForFunction(fDef), bodyPath, bodyTree));

    if (fDef->typeAnnotation) {
        auto argLoc = FileLocations::ensure(nodeStack.last().fileLocations,
                                            Path().withField(Fields::returnType));
        const Path pathToReturnType = Path().withField(Fields::scriptElement);

        Q_SCRIPTELEMENT_EXIT_IF(!stackHasScriptVariant());
        ScriptElementVariant variant = currentScriptNodeEl().takeVariant();
        finalizeScriptExpression(variant, pathToReturnType, argLoc);
        m.returnType->setScriptElement(variant);
        removeCurrentScriptNode({});
    }
    if (fDef->formals) {
        Q_SCRIPTELEMENT_EXIT_IF(!stackHasScriptList());
        const auto parameterList = scriptNodeStack.takeLast().takeList();
        const auto &parameterQList = parameterList.qList();
        size_t size = (size_t)parameterQList.size();
        for (size_t idx = size - 1; idx < size; --idx) {
            auto argLoc = FileLocations::ensure(
                    nodeStack.last().fileLocations,
                    Path().withField(Fields::parameters).withIndex(idx).withField(Fields::value));
            const Path pathToArgument = Path().withField(Fields::scriptElement);

            ScriptElementVariant variant = parameterQList[idx];
            setFormalParameterKind(variant);
            finalizeScriptExpression(variant, pathToArgument, argLoc);
            m.parameters[idx].value->setScriptElement(variant);
        }
    }

    // there should be no more uncollected script elements
    if (m_enableScriptExpressions && !scriptNodeStack.empty()) {
        Q_SCRIPTELEMENT_DISABLE();
    }
}

void QQmlDomAstCreatorBase::endVisit(AST::UiSourceElement *el)
{
    if (AST::cast<VariableStatement *>(el->sourceElement))
        return;
    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 QQmlDomAstCreatorBase::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.withField(Fields::value).withIndex(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);
        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.withField(Fields::nameIdentifiers), rootMap));
    }
    pushEl(sPathFromOwner, *sPtr, el);

    if (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 QQmlDomAstCreatorBase::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::fromField(Fields::objects))
                std::get<QmlComponent>(containingObject.value).m_objects[idx] = obj;
            else
                Q_UNREACHABLE();
            break;
        case DomType::QmlObject:
            if (p[p.length() - 2] == Path::fromField(Fields::children))
                std::get<QmlObject>(containingObject.value).m_children[idx] = obj;
            else
                Q_UNREACHABLE();
            break;
        default:
            Q_UNREACHABLE();
        }
    }
    removeCurrentNode(DomType::QmlObject);
}

void QQmlDomAstCreatorBase::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.withField(Fields::bindingIdentifiers), rootMap));
}

bool QQmlDomAstCreatorBase::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.withField(Fields::value).withField(Fields::nameIdentifiers), rootMap));
    }

    objValue->addPrototypePath(Paths::lookupTypePath(objValue->name()));
    pushEl(bPathFromOwner.withField(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 QQmlDomAstCreatorBase::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 QQmlDomAstCreatorBase::visit(AST::UiScriptBinding *el)
{
    ++m_nestedFunctionDepth;
    QStringView code = qmlFilePtr->code();
    SourceLocation loc = combineLocations(el->statement);
    const 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().withPath(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()) {
                const auto m = "id attributes should only be a lower case letter or an underscore "
                               "followed by letters, numbers or underscores, not %1";
                qmlFile.addError(std::move(
                        astParseErrors().warning(tr(m).arg(iExp->name)).withPath(pathFromOwner)));
            }
        } else {
            pathFromOwner =
                    current<QmlObject>().addBinding(bindingV, AddOption::KeepExisting, &bindingPtr);
            Q_ASSERT_X(bindingPtr, className, "binding could not be retrieved");
            const auto m = "id attributes should only be a lower case letter or an underscore "
                           "followed by letters, numbers or underscores, not %1 %2, assuming they "
                           "refer to a property";
            qmlFile.addError(std::move(
                    astParseErrors()
                            .warning(tr(m).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 QQmlDomAstCreatorBase::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().withField(Fields::value));
        value->setScriptElement(finalizeScriptExpression(currentScriptNodeEl().takeVariant(),
                                                         Path().withField(Fields::scriptElement),
                                                         valueLoc));
        removeCurrentScriptNode({});
    }
};

void QQmlDomAstCreatorBase::endVisit(AST::UiScriptBinding *)
{
    --m_nestedFunctionDepth;
    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 QQmlDomAstCreatorBase::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::fromField(Fields::value), nullptr);
    FileLocations::addRegion(arrayList, LeftBracketRegion, el->lbracketToken);
    FileLocations::addRegion(arrayList, RightBracketRegion, el->rbracketToken);
    arrayBindingLevels.append(nodeStack.size());
    return true;
}

void QQmlDomAstCreatorBase::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);
}

void QQmlDomAstCreatorBase::endVisit(AST::ArgumentList *list)
{
    endVisitForLists(list);
}

bool QQmlDomAstCreatorBase::visit(AST::UiParameterList *)
{
    return false; // do not create script node for Ui stuff
}

void QQmlDomAstCreatorBase::endVisit(AST::PatternElementList *list)
{
    endVisitForLists<AST::PatternElementList>(list, [](AST::PatternElementList *current) {
        int toCollect = 0;
        toCollect += bool(current->elision);
        toCollect += bool(current->element);
        return toCollect;
    });
}

void QQmlDomAstCreatorBase::endVisit(AST::PatternPropertyList *list)
{
    endVisitForLists(list);
}

/*!
   \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 QQmlDomAstCreatorBase::scriptElementForQualifiedId(AST::UiQualifiedId *expression)
{
    auto id = std::make_shared<ScriptElements::IdentifierExpression>(
            expression->firstSourceLocation(), expression->lastSourceLocation());
    id->setName(expression->toString());

    return ScriptElementVariant::fromElement(id);
}

bool QQmlDomAstCreatorBase::visit(AST::UiQualifiedId *)
{
    if (!m_enableScriptExpressions)
        return false;

    return false;
}

bool QQmlDomAstCreatorBase::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);
    FileLocations::addRegion(nodeStack.last().fileLocations, LeftBraceRegion, el->lbraceToken);
    FileLocations::addRegion(nodeStack.last().fileLocations, RightBraceRegion, el->rbraceToken);
    loadAnnotations(el);
    return true;
}

void QQmlDomAstCreatorBase::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 QQmlDomAstCreatorBase::visit(AST::UiEnumMemberList *el)
{
    EnumItem it(el->member.toString(), el->value,
                el->valueToken.isValid() ? EnumItem::ValueKind::ExplicitValue
                                         : EnumItem::ValueKind::ImplicitValue);
    EnumDecl &eDecl = std::get<EnumDecl>(currentNode().value);
    Path itPathFromDecl = eDecl.addValue(it);
    const auto map = createMap(DomType::EnumItem, itPathFromDecl, nullptr);
    if (el->commaToken.isValid())
        FileLocations::addRegion(map, CommaTokenRegion, el->commaToken);
    if (el->memberToken.isValid())
        FileLocations::addRegion(map, IdentifierRegion, el->memberToken);
    if (el->equalToken.isValid())
        FileLocations::addRegion(map, EqualTokenRegion, el->equalToken);
    if (el->valueToken.isValid())
        FileLocations::addRegion(map, EnumValueRegion, el->valueToken);
    FileLocations::addRegion(
            map, MainRegion, combine(combine(el->memberToken, el->commaToken), el->valueToken));
    return true;
}

void QQmlDomAstCreatorBase::endVisit(AST::UiEnumMemberList *el)
{
    Node::accept(el->next, this); // put other enum members at the same level as this one...
}

bool QQmlDomAstCreatorBase::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.withField(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 QQmlDomAstCreatorBase::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 QQmlDomAstCreatorBase::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 QQmlDomAstCreatorBase::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 QQmlDomAstCreatorBase::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 QQmlDomAstCreatorBase::throwRecursionDepthError()
{
    qmlFile.addError(astParseErrors().error(
            tr("Maximum statement or expression depth exceeded in QmlDomAstCreator")));
}

void QQmlDomAstCreatorBase::endVisit(AST::StatementList *list)
{
    endVisitForLists(list);
}

bool QQmlDomAstCreatorBase::visit(AST::BinaryExpression *)
{
    if (!m_enableScriptExpressions)
        return false;

    return true;
}

void QQmlDomAstCreatorBase::endVisit(AST::BinaryExpression *exp)
{
    if (!m_enableScriptExpressions)
        return;

    auto current = makeScriptElement<ScriptElements::BinaryExpression>(exp);
    current->addLocation(OperatorTokenRegion, exp->operatorToken);
    Q_SCRIPTELEMENT_EXIT_IF(!stackHasScriptVariant());
    current->setRight(currentScriptNodeEl().takeVariant());
    removeCurrentScriptNode({});
    Q_SCRIPTELEMENT_EXIT_IF(!stackHasScriptVariant());
    current->setLeft(currentScriptNodeEl().takeVariant());
    removeCurrentScriptNode({});

    pushScriptElement(current);
}

bool QQmlDomAstCreatorBase::visit(AST::Block *)
{
    if (!m_enableScriptExpressions)
        return false;

    return true;
}

void QQmlDomAstCreatorBase::endVisit(AST::Block *block)
{
    if (!m_enableScriptExpressions)
        return;

    auto current = makeScriptElement<ScriptElements::BlockStatement>(block);

    if (block->statements) {
        Q_SCRIPTELEMENT_EXIT_IF(!stackHasScriptList());
        current->setStatements(currentScriptNodeEl().takeList());
        removeCurrentScriptNode(DomType::List);
    }

    pushScriptElement(current);
}

bool QQmlDomAstCreatorBase::visit(AST::ForStatement *)
{
    if (!m_enableScriptExpressions)
        return false;

    return true;
}

void QQmlDomAstCreatorBase::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(!stackHasScriptVariant());
        current->setBody(currentScriptNodeEl().takeVariant());
        removeCurrentScriptNode(std::nullopt);
    }

    if (forStatement->expression) {
        Q_SCRIPTELEMENT_EXIT_IF(!stackHasScriptVariant());
        current->setExpression(currentScriptNodeEl().takeVariant());
        removeCurrentScriptNode(std::nullopt);
    }

    if (forStatement->condition) {
        Q_SCRIPTELEMENT_EXIT_IF(!stackHasScriptVariant());
        current->setCondition(currentScriptNodeEl().takeVariant());
        removeCurrentScriptNode(std::nullopt);
    }

    if (forStatement->declarations) {
        Q_SCRIPTELEMENT_EXIT_IF(!stackHasScriptList());
        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(!stackHasScriptVariant());
        current->setInitializer(currentScriptNodeEl().takeVariant());
        removeCurrentScriptNode(std::nullopt);
    }
    pushScriptElement(current);
}

bool QQmlDomAstCreatorBase::visit(AST::IdentifierExpression *expression)
{
    if (!m_enableScriptExpressions)
        return false;

    auto current = makeScriptElement<ScriptElements::IdentifierExpression>(expression);
    current->setName(expression->name);
    pushScriptElement(current);
    return true;
}

bool QQmlDomAstCreatorBase::visit(AST::NumericLiteral *expression)
{
    if (!m_enableScriptExpressions)
        return false;

    auto current = makeScriptElement<ScriptElements::Literal>(expression);
    current->setLiteralValue(expression->value);
    pushScriptElement(current);
    return true;
}

bool QQmlDomAstCreatorBase::visit(AST::StringLiteral *expression)
{
    if (!m_enableScriptExpressions)
        return false;

    pushScriptElement(makeStringLiteral(expression->value, expression));
    return true;
}

bool QQmlDomAstCreatorBase::visit(AST::NullExpression *expression)
{
    if (!m_enableScriptExpressions)
        return false;

    auto current = makeScriptElement<ScriptElements::Literal>(expression);
    current->setLiteralValue(nullptr);
    pushScriptElement(current);
    return true;
}

bool QQmlDomAstCreatorBase::visit(AST::TrueLiteral *expression)
{
    if (!m_enableScriptExpressions)
        return false;

    auto current = makeScriptElement<ScriptElements::Literal>(expression);
    current->setLiteralValue(true);
    pushScriptElement(current);
    return true;
}

bool QQmlDomAstCreatorBase::visit(AST::FalseLiteral *expression)
{
    if (!m_enableScriptExpressions)
        return false;

    auto current = makeScriptElement<ScriptElements::Literal>(expression);
    current->setLiteralValue(false);
    pushScriptElement(current);
    return true;
}

bool QQmlDomAstCreatorBase::visit(AST::IdentifierPropertyName *expression)
{
    if (!m_enableScriptExpressions)
        return false;

    auto current = makeScriptElement<ScriptElements::IdentifierExpression>(expression);
    current->setName(expression->id);
    pushScriptElement(current);
    return true;
}

bool QQmlDomAstCreatorBase::visit(AST::StringLiteralPropertyName *expression)
{
    if (!m_enableScriptExpressions)
        return false;

    pushScriptElement(makeStringLiteral(expression->id, expression));
    return true;
}

bool QQmlDomAstCreatorBase::visit(AST::TypeAnnotation *)
{
    if (!m_enableScriptExpressions)
        return false;

    // do nothing: the work is done in (end)visit(AST::Type*).
    return true;
}

bool QQmlDomAstCreatorBase::visit(AST::RegExpLiteral *literal)
{
    if (!m_enableScriptExpressions)
        return false;

    auto current = makeGenericScriptElement(literal, DomType::ScriptRegExpLiteral);
    current->insertValue(Fields::regExpPattern, literal->pattern);
    current->insertValue(Fields::regExpFlags, literal->flags);
    pushScriptElement(current);

    return true;
}

bool QQmlDomAstCreatorBase::visit(AST::ThisExpression *expression)
{
    if (!m_enableScriptExpressions)
        return false;

    auto current = makeGenericScriptElement(expression, DomType::ScriptThisExpression);
    if (expression->thisToken.isValid())
        current->addLocation(ThisKeywordRegion, expression->thisToken);
    pushScriptElement(current);
    return true;
}

bool QQmlDomAstCreatorBase::visit(AST::SuperLiteral *expression)
{
    if (!m_enableScriptExpressions)
        return false;

    auto current = makeGenericScriptElement(expression, DomType::ScriptSuperLiteral);
    if (expression->superToken.isValid())
        current->addLocation(SuperKeywordRegion, expression->superToken);
    pushScriptElement(current);
    return true;
}

bool QQmlDomAstCreatorBase::visit(AST::NumericLiteralPropertyName *expression)
{
    if (!m_enableScriptExpressions)
        return false;

    auto current = makeScriptElement<ScriptElements::Literal>(expression);
    current->setLiteralValue(expression->id);
    pushScriptElement(current);
    return true;
}

bool QQmlDomAstCreatorBase::visit(AST::ComputedPropertyName *)
{
    if (!m_enableScriptExpressions)
        return false;

    // nothing to do, just forward the underlying expression without changing/wrapping it
    return true;
}

template<typename T>
void QQmlDomAstCreatorBase::endVisitForLists(T *list,
                                         const std::function<int(T *)> &scriptElementsPerEntry)
{
    if (!m_enableScriptExpressions)
        return;

    auto current = makeScriptList(list);
    for (auto it = list; it; it = it->next) {
        const int entriesToCollect = scriptElementsPerEntry ? scriptElementsPerEntry(it) : 1;
        for (int i = 0; i < entriesToCollect; ++i) {
            Q_SCRIPTELEMENT_EXIT_IF(scriptNodeStack.isEmpty());
            auto last = scriptNodeStack.takeLast();
            if (last.isList())
                current.append(last.takeList());
            else
                current.append(last.takeVariant());
        }
    }

    current.reverse();
    pushScriptElement(current);
}

void QQmlDomAstCreatorBase::endVisit(AST::VariableDeclarationList *list)
{
    endVisitForLists(list);
}

bool QQmlDomAstCreatorBase::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 QQmlDomAstCreatorBase::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 QQmlDomAstCreatorBase::endVisitHelper(
        AST::PatternElement *pe,
        const std::shared_ptr<ScriptElements::GenericScriptElement> &current)
{
    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(!stackHasScriptVariant());
        current->insertChild(Fields::initializer, scriptNodeStack.last().takeVariant());
        scriptNodeStack.removeLast();
    }
    if (pe->typeAnnotation) {
        Q_SCRIPTELEMENT_EXIT_IF(!stackHasScriptVariant());
        current->insertChild(Fields::type, scriptNodeStack.last().takeVariant());
        scriptNodeStack.removeLast();
    }
    if (pe->bindingTarget) {
        Q_SCRIPTELEMENT_EXIT_IF(!stackHasScriptVariant());
        current->insertChild(Fields::bindingElement, scriptNodeStack.last().takeVariant());
        scriptNodeStack.removeLast();
    }
}

void QQmlDomAstCreatorBase::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 QQmlDomAstCreatorBase::visit(AST::IfStatement *)
{
    if (!m_enableScriptExpressions)
        return false;

    return true;
}

void QQmlDomAstCreatorBase::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(!stackHasScriptVariant());
        current->setAlternative(scriptNodeStack.last().takeVariant());
        scriptNodeStack.removeLast();
    }

    if (ifStatement->ok) {
        Q_SCRIPTELEMENT_EXIT_IF(!stackHasScriptVariant());
        current->setConsequence(scriptNodeStack.last().takeVariant());
        scriptNodeStack.removeLast();
    }
    if (ifStatement->expression) {
        Q_SCRIPTELEMENT_EXIT_IF(!stackHasScriptVariant());
        current->setCondition(scriptNodeStack.last().takeVariant());
        scriptNodeStack.removeLast();
    }

    pushScriptElement(current);
}

bool QQmlDomAstCreatorBase::visit(AST::ReturnStatement *)
{
    if (!m_enableScriptExpressions)
        return false;

    return true;
}

void QQmlDomAstCreatorBase::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(!stackHasScriptVariant());
        current->setExpression(currentScriptNodeEl().takeVariant());
        removeCurrentScriptNode({});
    }

    pushScriptElement(current);
}

bool QQmlDomAstCreatorBase::visit(AST::YieldExpression *)
{
    if (!m_enableScriptExpressions)
        return false;

    return true;
}

void QQmlDomAstCreatorBase::endVisit(AST::YieldExpression *yExpression)
{
    if (!m_enableScriptExpressions)
        return;

    auto current = makeGenericScriptElement(yExpression, DomType::ScriptYieldExpression);
    current->addLocation(YieldKeywordRegion, yExpression->yieldToken);

    if (yExpression->expression) {
        Q_SCRIPTELEMENT_EXIT_IF(!stackHasScriptVariant());
        current->insertChild(Fields::expression, currentScriptNodeEl().takeVariant());
        removeCurrentScriptNode({});
    }

    pushScriptElement(current);
}

bool QQmlDomAstCreatorBase::visit(AST::FieldMemberExpression *)
{
    if (!m_enableScriptExpressions)
        return false;

    return true;
}

void QQmlDomAstCreatorBase::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(!stackHasScriptVariant());
        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 QQmlDomAstCreatorBase::visit(AST::ArrayMemberExpression *)
{
    if (!m_enableScriptExpressions)
        return false;

    return true;
}

void QQmlDomAstCreatorBase::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(!stackHasScriptVariant());
        // 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(!stackHasScriptVariant());
        current->setLeft(currentScriptNodeEl().takeVariant());
        removeCurrentScriptNode({});
    }

    pushScriptElement(current);
}

bool QQmlDomAstCreatorBase::visit(AST::CallExpression *)
{
    if (!m_enableScriptExpressions)
        return false;

    return true;
}

void QQmlDomAstCreatorBase::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(!stackHasScriptList());
        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(!stackHasScriptVariant());
        current->insertChild(Fields::callee, currentScriptNodeEl().takeVariant());
        removeCurrentScriptNode({});
    }

    pushScriptElement(current);
}

bool QQmlDomAstCreatorBase::visit(AST::ArrayPattern *)
{
    if (!m_enableScriptExpressions)
        return false;

    return true;
}

void QQmlDomAstCreatorBase::endVisit(AST::ArrayPattern *exp)
{
    if (!m_enableScriptExpressions)
        return;

    auto current = makeGenericScriptElement(exp, DomType::ScriptArray);

    if (exp->elements) {
        Q_SCRIPTELEMENT_EXIT_IF(!stackHasScriptList());
        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 QQmlDomAstCreatorBase::visit(AST::ObjectPattern *)
{
    if (!m_enableScriptExpressions)
        return false;

    return true;
}

void QQmlDomAstCreatorBase::endVisit(AST::ObjectPattern *exp)
{
    if (!m_enableScriptExpressions)
        return;

    auto current = makeGenericScriptElement(exp, DomType::ScriptObject);

    if (exp->properties) {
        Q_SCRIPTELEMENT_EXIT_IF(!stackHasScriptList());
        current->insertChild(Fields::properties, currentScriptNodeEl().takeList());
        removeCurrentScriptNode({});
    } else {
        // insert empty list
        current->insertChild(Fields::properties,
                             ScriptElements::ScriptList(exp->lbraceToken, exp->rbraceToken));
    }

    pushScriptElement(current);
}

bool QQmlDomAstCreatorBase::visit(AST::PatternProperty *)
{
    if (!m_enableScriptExpressions)
        return false;

    return true;
}

void QQmlDomAstCreatorBase::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(!stackHasScriptVariant());
        current->insertChild(Fields::name, currentScriptNodeEl().takeVariant());
        removeCurrentScriptNode({});
    }

    pushScriptElement(current);
}

bool QQmlDomAstCreatorBase::visit(AST::VariableStatement *)
{
    if (!m_enableScriptExpressions)
        return false;

    return true;
}

void QQmlDomAstCreatorBase::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(!stackHasScriptList());

        ScriptElements::ScriptList list = currentScriptNodeEl().takeList();
        list.replaceKindForGenericChildren(DomType::ScriptPattern,
                                           DomType::ScriptVariableDeclarationEntry);
        current->insertChild(Fields::declarations, std::move(list));

        removeCurrentScriptNode({});
    }

    pushScriptElement(current);
}

bool QQmlDomAstCreatorBase::visit(AST::Type *)
{
    if (!m_enableScriptExpressions)
        return false;

    return true;
}

void QQmlDomAstCreatorBase::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 QQmlDomAstCreatorBase::visit(AST::DefaultClause *)
{
    if (!m_enableScriptExpressions)
        return false;

    return true;
}

void QQmlDomAstCreatorBase::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(!stackHasScriptList());
        current->insertChild(Fields::statements, currentScriptNodeEl().takeList());
        removeCurrentScriptNode({});
    }

    pushScriptElement(current);
}

bool QQmlDomAstCreatorBase::visit(AST::CaseClause *)
{
    if (!m_enableScriptExpressions)
        return false;

    return true;
}

void QQmlDomAstCreatorBase::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(!stackHasScriptList());
        current->insertChild(Fields::statements, currentScriptNodeEl().takeList());
        removeCurrentScriptNode({});
    }

    if (exp->expression) {
        Q_SCRIPTELEMENT_EXIT_IF(!stackHasScriptVariant());
        current->insertChild(Fields::expression, currentScriptNodeEl().takeVariant());
        removeCurrentScriptNode({});
    }

    pushScriptElement(current);
}

bool QQmlDomAstCreatorBase::visit(AST::CaseClauses *)
{
    if (!m_enableScriptExpressions)
        return false;

    return true;
}

void QQmlDomAstCreatorBase::endVisit(AST::CaseClauses *list)
{
    if (!m_enableScriptExpressions)
        return;

    auto current = makeScriptList(list);

    for (auto it = list; it; it = it->next) {
        Q_SCRIPTELEMENT_EXIT_IF(!stackHasScriptVariant());
        current.append(scriptNodeStack.takeLast().takeVariant());
    }

    current.reverse();
    pushScriptElement(current);
}

bool QQmlDomAstCreatorBase::visit(AST::CaseBlock *)
{
    if (!m_enableScriptExpressions)
        return false;

    return true;
}

void QQmlDomAstCreatorBase::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(!stackHasScriptList());
        current->insertChild(Fields::moreCaseClauses, currentScriptNodeEl().takeList());
        removeCurrentScriptNode({});
    }

    if (exp->defaultClause) {
        Q_SCRIPTELEMENT_EXIT_IF(!stackHasScriptVariant());
        current->insertChild(Fields::defaultClause, currentScriptNodeEl().takeVariant());
        removeCurrentScriptNode({});
    }

    if (exp->clauses) {
        Q_SCRIPTELEMENT_EXIT_IF(!stackHasScriptList());
        current->insertChild(Fields::caseClauses, currentScriptNodeEl().takeList());
        removeCurrentScriptNode({});
    }
    pushScriptElement(current);
}

bool QQmlDomAstCreatorBase::visit(AST::SwitchStatement *)
{
    if (!m_enableScriptExpressions)
        return false;

    return true;
}

void QQmlDomAstCreatorBase::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(!stackHasScriptVariant());
        current->insertChild(Fields::caseBlock, currentScriptNodeEl().takeVariant());
        removeCurrentScriptNode({});
    }
    if (exp->expression) {
        Q_SCRIPTELEMENT_EXIT_IF(!stackHasScriptVariant());
        current->insertChild(Fields::expression, currentScriptNodeEl().takeVariant());
        removeCurrentScriptNode({});
    }

    pushScriptElement(current);
}

bool QQmlDomAstCreatorBase::visit(AST::WhileStatement *)
{
    if (!m_enableScriptExpressions)
        return false;

    return true;
}

void QQmlDomAstCreatorBase::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(!stackHasScriptVariant());
        current->insertChild(Fields::body, currentScriptNodeEl().takeVariant());
        removeCurrentScriptNode({});
    }

    if (exp->expression) {
        Q_SCRIPTELEMENT_EXIT_IF(!stackHasScriptVariant());
        current->insertChild(Fields::expression, currentScriptNodeEl().takeVariant());
        removeCurrentScriptNode({});
    }

    pushScriptElement(current);
}

bool QQmlDomAstCreatorBase::visit(AST::DoWhileStatement *)
{
    if (!m_enableScriptExpressions)
        return false;

    return true;
}

void QQmlDomAstCreatorBase::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(!stackHasScriptVariant());
        current->insertChild(Fields::expression, currentScriptNodeEl().takeVariant());
        removeCurrentScriptNode({});
    }

    if (exp->statement) {
        Q_SCRIPTELEMENT_EXIT_IF(!stackHasScriptVariant());
        current->insertChild(Fields::body, currentScriptNodeEl().takeVariant());
        removeCurrentScriptNode({});
    }

    pushScriptElement(current);
}

bool QQmlDomAstCreatorBase::visit(AST::ForEachStatement *)
{
    if (!m_enableScriptExpressions)
        return false;

    return true;
}

void QQmlDomAstCreatorBase::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(!stackHasScriptVariant());
        current->insertChild(Fields::body, currentScriptNodeEl().takeVariant());
        removeCurrentScriptNode({});
    }
    if (exp->expression) {
        Q_SCRIPTELEMENT_EXIT_IF(!stackHasScriptVariant());
        current->insertChild(Fields::expression, currentScriptNodeEl().takeVariant());
        removeCurrentScriptNode({});
    }

    if (exp->lhs) {
        Q_SCRIPTELEMENT_EXIT_IF(!stackHasScriptVariant());
        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 QQmlDomAstCreatorBase::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 QQmlDomAstCreatorBase::endVisit(AST::ClassExpression *)
{
}

void QQmlDomAstCreatorBase::endVisit(AST::TaggedTemplate *literal)
{
    if (!m_enableScriptExpressions)
        return;
    auto current = makeGenericScriptElement(literal, DomType::ScriptTaggedTemplate);
    Q_SCRIPTELEMENT_EXIT_IF(!stackHasScriptVariant());
    current->insertChild(Fields::templateLiteral, scriptNodeStack.takeLast().takeVariant());
    Q_SCRIPTELEMENT_EXIT_IF(!stackHasScriptVariant());
    current->insertChild(Fields::callee, scriptNodeStack.takeLast().takeVariant());
    pushScriptElement(current);
}

/*!
\internal
Denotes the position of a template part in a template string. For example, in \c{`a${b}c${d}`}, \c a
is \c AtBeginning and \c{${d}} is \c AtEnd while the others are \c InMiddle, and in \c{`a`}, \c a is
\c AtBeginning and \c AtEnd.
 */
enum TemplatePartPosition : quint8 {
    InMiddle = 0,
    AtBeginning = 0x1,
    AtEnd = 0x2,
};

Q_DECLARE_FLAGS(TemplatePartPositions, TemplatePartPosition)

/*!
\internal
Sets the DollarLeftBraceTokenRegion sourcelocation in currentExpression if templatePartLocation
claims that toBeSplit ends in \c{${}.
*/
static void extractDollarBraceSourceLocationInto(
        const std::shared_ptr<ScriptElements::GenericScriptElement> &currentExpression,
        const SourceLocation &toBeSplit, QStringView code,
        TemplatePartPositions templatePartLocation)
{
    if (templatePartLocation & AtEnd || !currentExpression)
        return;

    const auto offset = toBeSplit.offset + toBeSplit.length - 2;
    constexpr auto length = quint32(std::char_traits<char>::length("${"));
    const auto [row, column] = SourceLocation::rowAndColumnFrom(code, offset, toBeSplit);
    currentExpression->addLocation(FileLocationRegion::DollarLeftBraceTokenRegion,
                                   SourceLocation{ offset, length, row, column });
    return;
}

/*!
\internal
See also \l extractDollarBraceSourceLocationInto.
*/
static void extractRightBacktickSourceLocationInto(
        const std::shared_ptr<ScriptElements::GenericScriptElement> &currentTemplate,
        const SourceLocation &toBeSplit, QStringView code,
        TemplatePartPositions templatePartLocation)
{
    if (!(templatePartLocation & AtEnd))
        return;

    const auto offset = toBeSplit.offset + toBeSplit.length - 1;
    constexpr auto length = quint32(std::char_traits<char>::length("`"));
    const auto [row, column] = SourceLocation::rowAndColumnFrom(code, offset, toBeSplit);
    currentTemplate->addLocation(FileLocationRegion::RightBacktickTokenRegion,
                                 SourceLocation{ offset, length, row, column });
}

/*!
\internal
See also \l extractDollarBraceSourceLocationInto.
*/
static void extractLeftBacktickSourceLocationInto(
        const std::shared_ptr<ScriptElements::GenericScriptElement> &currentTemplate,
        const SourceLocation &toBeSplit, TemplatePartPositions templatePartLocation)
{
    if (!(templatePartLocation & AtBeginning))
        return;

    constexpr auto length = quint32(std::char_traits<char>::length("`"));
    const QQmlJS::SourceLocation leftBacktick{ toBeSplit.offset, length, toBeSplit.startLine,
                                               toBeSplit.startColumn };
    currentTemplate->addLocation(FileLocationRegion::LeftBacktickTokenRegion, leftBacktick);
}

/*!
\internal
See also \l extractDollarBraceSourceLocationInto, but returns the extracted right brace instead of
inserting right away.
*/
static SourceLocation extractRightBraceSourceLocation(const SourceLocation &toBeSplit,
                                                      TemplatePartPositions templatePartLocation)
{
    if (templatePartLocation & AtBeginning)
        return SourceLocation{};

    // extract } at the beginning and insert in next loop iteration
    return SourceLocation{ toBeSplit.offset, 1, toBeSplit.startLine, toBeSplit.startColumn };
}

/*!
\internal
Cleans the toBeSplit sourcelocation from potential backticks, dollar braces and right braces to only
contain the location of the string part.
*/
static SourceLocation extractStringLocation(const SourceLocation &toBeSplit, QStringView code,
                                            TemplatePartPositions location)
{

    // remove "`" or "}" at beginning and "`" or "${" at the end of this location.
    const quint32 length = toBeSplit.length - (location & AtEnd ? 2 : 3);
    const quint32 offset = toBeSplit.offset + 1;
    const auto [row, column] = SourceLocation::rowAndColumnFrom(code, offset, toBeSplit);
    return SourceLocation{ offset, length, row, column };
}

void QQmlDomAstCreatorBase::endVisit(AST::TemplateLiteral *literal)
{
    if (!m_enableScriptExpressions)
        return;

    // AST::TemplateLiteral is a list and a TemplateLiteral at the same time:
    // in the Dom representation wrap the list into a separate TemplateLiteral Item.
    auto currentList = makeScriptList(literal);
    auto currentTemplate = makeGenericScriptElement(literal, DomType::ScriptTemplateLiteral);

    const auto children = [&literal]() {
        std::vector<AST::TemplateLiteral *> result;
        for (auto it = literal; it; it = it->next) {
            result.push_back(it);
        }
        return result;
    }();

    SourceLocation rightBrace;
    for (auto it = children.crbegin(); it != children.crend(); ++it) {
        // literalToken contains "`", "${", "}", for example "`asdf${" or "}asdf${"
        const QQmlJS::SourceLocation toBeSplit = (*it)->literalToken;
        std::shared_ptr<ScriptElements::GenericScriptElement> currentExpression;

        if ((*it)->expression) {
            Q_SCRIPTELEMENT_EXIT_IF(!stackHasScriptVariant());
            currentExpression = makeGenericScriptElement((*it)->expression,
                                                         DomType::ScriptTemplateExpressionPart);

            currentExpression->insertChild(Fields::expression,
                                           scriptNodeStack.takeLast().takeVariant());
            if (rightBrace.isValid()) {
                currentExpression->addLocation(FileLocationRegion::RightBraceRegion,
                                               std::exchange(rightBrace, SourceLocation{}));
            }
            currentList.append(ScriptElementVariant::fromElement(currentExpression));
        }

        if (!toBeSplit.isValid())
            continue;

        const TemplatePartPositions location = [&it, &children]() {
            TemplatePartPositions result;
            if (it == children.crbegin())
                result |= AtEnd;
            if (it == std::prev(children.crend()))
                result |= AtBeginning;
            return result;
        }();

        extractRightBacktickSourceLocationInto(currentTemplate, toBeSplit, qmlFilePtr->code(),
                                               location);
        extractLeftBacktickSourceLocationInto(currentTemplate, toBeSplit, location);

        extractDollarBraceSourceLocationInto(currentExpression, toBeSplit, qmlFilePtr->code(),
                                             location);
        rightBrace = extractRightBraceSourceLocation(toBeSplit, location);

        if ((*it)->rawValue.isEmpty())
            continue;

        const SourceLocation stringLocation =
                extractStringLocation(toBeSplit, qmlFilePtr->code(), location);
        auto currentString =
                makeGenericScriptElement(stringLocation, DomType::ScriptTemplateStringPart);
        currentString->insertValue(Fields::value, (*it)->rawValue);

        currentList.append(ScriptElementVariant::fromElement(currentString));
    }
    currentList.reverse();

    currentTemplate->insertChild(Fields::components, currentList);
    pushScriptElement(currentTemplate);
}

bool QQmlDomAstCreatorBase::visit(AST::TryStatement *)
{
    return m_enableScriptExpressions;
}

void QQmlDomAstCreatorBase::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(!stackHasScriptVariant());
        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(!stackHasScriptVariant());
        current->insertChild(Fields::catchBlock, currentScriptNodeEl().takeVariant());
        removeCurrentScriptNode({});
        Q_SCRIPTELEMENT_EXIT_IF(!stackHasScriptVariant());
        current->insertChild(Fields::catchParameter, currentScriptNodeEl().takeVariant());
        removeCurrentScriptNode({});
    }

    if (statement->statement) {
        Q_SCRIPTELEMENT_EXIT_IF(!stackHasScriptVariant());
        current->insertChild(Fields::block, currentScriptNodeEl().takeVariant());
        removeCurrentScriptNode({});
    }

    pushScriptElement(current);
}

bool QQmlDomAstCreatorBase::visit(AST::Catch *)
{
    // handled in visit(AST::TryStatement* )
    return m_enableScriptExpressions;
}

void QQmlDomAstCreatorBase::endVisit(AST::Catch *)
{
    // handled in endVisit(AST::TryStatement* )
}

bool QQmlDomAstCreatorBase::visit(AST::Finally *)
{
    // handled in visit(AST::TryStatement* )
    return m_enableScriptExpressions;
}

void QQmlDomAstCreatorBase::endVisit(AST::Finally *)
{
    // handled in endVisit(AST::TryStatement* )
}

bool QQmlDomAstCreatorBase::visit(AST::ThrowStatement *)
{
    return m_enableScriptExpressions;
}

void QQmlDomAstCreatorBase::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(!stackHasScriptVariant());
        current->insertChild(Fields::expression, currentScriptNodeEl().takeVariant());
        removeCurrentScriptNode({});
    }

    pushScriptElement(current);
}

bool QQmlDomAstCreatorBase::visit(AST::LabelledStatement *)
{
    return m_enableScriptExpressions;
}

void QQmlDomAstCreatorBase::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(!stackHasScriptVariant());
        current->insertChild(Fields::statement, currentScriptNodeEl().takeVariant());
        removeCurrentScriptNode({});
    }

    pushScriptElement(current);
}

bool QQmlDomAstCreatorBase::visit(AST::BreakStatement *)
{
    return m_enableScriptExpressions;
}

void QQmlDomAstCreatorBase::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);
}

bool QQmlDomAstCreatorBase::visit(AST::CommaExpression *)
{
    return m_enableScriptExpressions;
}

void QQmlDomAstCreatorBase::endVisit(AST::CommaExpression *commaExpression)
{
    if (!m_enableScriptExpressions)
        return;

    auto current = makeScriptElement<ScriptElements::BinaryExpression>(commaExpression);
    current->addLocation(OperatorTokenRegion, commaExpression->commaToken);

    if (commaExpression->right) {
        Q_SCRIPTELEMENT_EXIT_IF(!stackHasScriptVariant());
        current->setRight(currentScriptNodeEl().takeVariant());
        removeCurrentScriptNode({});
    }

    if (commaExpression->left) {
        Q_SCRIPTELEMENT_EXIT_IF(!stackHasScriptVariant());
        current->setLeft(currentScriptNodeEl().takeVariant());
        removeCurrentScriptNode({});
    }

    pushScriptElement(current);
}

bool QQmlDomAstCreatorBase::visit(AST::ConditionalExpression *)
{
    return m_enableScriptExpressions;
}

void QQmlDomAstCreatorBase::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(!stackHasScriptVariant());
        current->insertChild(Fields::alternative, currentScriptNodeEl().takeVariant());
        removeCurrentScriptNode({});
    }

    if (expression->ok) {
        Q_SCRIPTELEMENT_EXIT_IF(!stackHasScriptVariant());
        current->insertChild(Fields::consequence, currentScriptNodeEl().takeVariant());
        removeCurrentScriptNode({});
    }

    if (expression->expression) {
        Q_SCRIPTELEMENT_EXIT_IF(!stackHasScriptVariant());
        current->insertChild(Fields::condition, currentScriptNodeEl().takeVariant());
        removeCurrentScriptNode({});
    }

    pushScriptElement(current);
}

bool QQmlDomAstCreatorBase::visit(AST::ContinueStatement *)
{
    return m_enableScriptExpressions;
}

void QQmlDomAstCreatorBase::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>
QQmlDomAstCreatorBase::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 (!stackHasScriptVariant()) {
            Q_SCRIPTELEMENT_DISABLE();
            return {};
        }
        current->insertChild(Fields::expression, currentScriptNodeEl().takeVariant());
        removeCurrentScriptNode({});
    }

    return current;
}

bool QQmlDomAstCreatorBase::visit(AST::UnaryMinusExpression *)
{
    return m_enableScriptExpressions;
}

void QQmlDomAstCreatorBase::endVisit(AST::UnaryMinusExpression *statement)
{
    if (!m_enableScriptExpressions)
        return;

    auto current =
            makeUnaryExpression(statement, statement->minusToken, statement->expression, Prefix);
    if (!current)
        return;

    pushScriptElement(current);
}

bool QQmlDomAstCreatorBase::visit(AST::UnaryPlusExpression *)
{
    return m_enableScriptExpressions;
}

void QQmlDomAstCreatorBase::endVisit(AST::UnaryPlusExpression *statement)
{
    if (!m_enableScriptExpressions)
        return;

    auto current =
            makeUnaryExpression(statement, statement->plusToken, statement->expression, Prefix);
    if (!current)
        return;

    pushScriptElement(current);
}

bool QQmlDomAstCreatorBase::visit(AST::TildeExpression *)
{
    return m_enableScriptExpressions;
}

void QQmlDomAstCreatorBase::endVisit(AST::TildeExpression *statement)
{
    if (!m_enableScriptExpressions)
        return;

    auto current =
            makeUnaryExpression(statement, statement->tildeToken, statement->expression, Prefix);
    if (!current)
        return;

    pushScriptElement(current);
}

bool QQmlDomAstCreatorBase::visit(AST::NotExpression *)
{
    return m_enableScriptExpressions;
}

void QQmlDomAstCreatorBase::endVisit(AST::NotExpression *statement)
{
    if (!m_enableScriptExpressions)
        return;

    auto current =
            makeUnaryExpression(statement, statement->notToken, statement->expression, Prefix);
    if (!current)
        return;

    pushScriptElement(current);
}

bool QQmlDomAstCreatorBase::visit(AST::TypeOfExpression *)
{
    return m_enableScriptExpressions;
}

void QQmlDomAstCreatorBase::endVisit(AST::TypeOfExpression *statement)
{
    if (!m_enableScriptExpressions)
        return;

    auto current =
            makeUnaryExpression(statement, statement->typeofToken, statement->expression, Prefix);
    if (!current)
        return;

    pushScriptElement(current);
}

bool QQmlDomAstCreatorBase::visit(AST::DeleteExpression *)
{
    return m_enableScriptExpressions;
}

void QQmlDomAstCreatorBase::endVisit(AST::DeleteExpression *statement)
{
    if (!m_enableScriptExpressions)
        return;

    auto current =
            makeUnaryExpression(statement, statement->deleteToken, statement->expression, Prefix);
    if (!current)
        return;

    pushScriptElement(current);
}

bool QQmlDomAstCreatorBase::visit(AST::VoidExpression *)
{
    return m_enableScriptExpressions;
}

void QQmlDomAstCreatorBase::endVisit(AST::VoidExpression *statement)
{
    if (!m_enableScriptExpressions)
        return;

    auto current =
            makeUnaryExpression(statement, statement->voidToken, statement->expression, Prefix);
    if (!current)
        return;

    pushScriptElement(current);
}

bool QQmlDomAstCreatorBase::visit(AST::PostDecrementExpression *)
{
    return m_enableScriptExpressions;
}

void QQmlDomAstCreatorBase::endVisit(AST::PostDecrementExpression *statement)
{
    if (!m_enableScriptExpressions)
        return;

    auto current =
            makeUnaryExpression(statement, statement->decrementToken, statement->base, Postfix);
    if (!current)
        return;

    pushScriptElement(current);
}

bool QQmlDomAstCreatorBase::visit(AST::PostIncrementExpression *)
{
    return m_enableScriptExpressions;
}

void QQmlDomAstCreatorBase::endVisit(AST::PostIncrementExpression *statement)
{
    if (!m_enableScriptExpressions)
        return;

    auto current =
            makeUnaryExpression(statement, statement->incrementToken, statement->base, Postfix);
    if (!current)
        return;

    pushScriptElement(current);
}

bool QQmlDomAstCreatorBase::visit(AST::PreIncrementExpression *)
{
    return m_enableScriptExpressions;
}

void QQmlDomAstCreatorBase::endVisit(AST::PreIncrementExpression *statement)
{
    if (!m_enableScriptExpressions)
        return;

    auto current = makeUnaryExpression(statement, statement->incrementToken, statement->expression,
                                       Prefix);
    if (!current)
        return;

    pushScriptElement(current);
}

bool QQmlDomAstCreatorBase::visit(AST::EmptyStatement *)
{
    return m_enableScriptExpressions;
}

void QQmlDomAstCreatorBase::endVisit(AST::EmptyStatement *statement)
{
    if (!m_enableScriptExpressions)
        return;

    auto current = makeGenericScriptElement(statement, DomType::ScriptEmptyStatement);
    current->addLocation(FileLocationRegion::SemicolonTokenRegion, statement->semicolonToken);
    pushScriptElement(current);
}

bool QQmlDomAstCreatorBase::visit(AST::NestedExpression *)
{
    return m_enableScriptExpressions;
}

void QQmlDomAstCreatorBase::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(!stackHasScriptVariant());
        current->insertChild(Fields::expression, currentScriptNodeEl().takeVariant());
        removeCurrentScriptNode({});
    }

    pushScriptElement(current);
}

bool QQmlDomAstCreatorBase::visit(AST::NewExpression *)
{
    return m_enableScriptExpressions;
}

void QQmlDomAstCreatorBase::endVisit(AST::NewExpression *expression)
{
    if (!m_enableScriptExpressions)
        return;

    auto current = makeGenericScriptElement(expression, DomType::ScriptNewExpression);
    current->addLocation(FileLocationRegion::NewKeywordRegion, expression->newToken);

    if (expression->expression) {
        Q_SCRIPTELEMENT_EXIT_IF(!stackHasScriptVariant());
        current->insertChild(Fields::expression, currentScriptNodeEl().takeVariant());
        removeCurrentScriptNode({});
    }

    pushScriptElement(current);
}

bool QQmlDomAstCreatorBase::visit(AST::NewMemberExpression *)
{
    return m_enableScriptExpressions;
}

void QQmlDomAstCreatorBase::endVisit(AST::NewMemberExpression *expression)
{
    if (!m_enableScriptExpressions)
        return;

    auto current = makeGenericScriptElement(expression, DomType::ScriptNewMemberExpression);
    current->addLocation(FileLocationRegion::NewKeywordRegion, expression->newToken);
    current->addLocation(FileLocationRegion::LeftParenthesisRegion, expression->lparenToken);
    current->addLocation(FileLocationRegion::RightParenthesisRegion, expression->rparenToken);

    if (expression->arguments) {
        Q_SCRIPTELEMENT_EXIT_IF(!stackHasScriptList());
        current->insertChild(Fields::arguments, scriptNodeStack.takeLast().takeList());
    }
    if (expression->base) {
        Q_SCRIPTELEMENT_EXIT_IF(!stackHasScriptVariant());
        current->insertChild(Fields::base, scriptNodeStack.takeLast().takeVariant());
    }

    pushScriptElement(current);
}

void QQmlDomAstCreatorBase::endVisit(AST::WithStatement *ast)
{
    if (!m_enableScriptExpressions)
        return;

    auto current = makeGenericScriptElement(ast, DomType::ScriptWithStatement);

    if (ast->statement) {
        Q_SCRIPTELEMENT_EXIT_IF(!stackHasScriptVariant());
        current->insertChild(Fields::statement, scriptNodeStack.takeLast().takeVariant());
    }
    if (ast->expression) {
        Q_SCRIPTELEMENT_EXIT_IF(!stackHasScriptVariant());
        current->insertChild(Fields::expression, scriptNodeStack.takeLast().takeVariant());
    }

    pushScriptElement(current);
}

bool QQmlDomAstCreatorBase::visit(AST::PreDecrementExpression *)
{
    return m_enableScriptExpressions;
}

void QQmlDomAstCreatorBase::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(MutableDomItem &qmlFile,
                                                                   QQmlJSLogger *logger,
                                                                   QQmlJSImporter *importer)
    : m_logger(logger),
      m_importer(importer),
      m_implicitImportDirectory(QQmlJSImportVisitor::implicitImportDirectory(
              m_logger->filePath(), m_importer->resourceFileMapper())),
      m_scopeCreator(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::ScriptIdentifierExpression: {
            // A ScriptIdentifierExpression in a QML scope is an actual ID.
            if (scope->scopeType() == QQmlJSScope::ScopeType::QMLScope)
                m_domCreator.currentScriptNodeEl().setSemanticScope(scope);
            break;
        }
        case DomType::ScriptBlockStatement:
        case DomType::ScriptForStatement:
        case DomType::ScriptForEachStatement:
        case DomType::ScriptDoWhileStatement:
        case DomType::ScriptWhileStatement:
        case DomType::List:
            m_domCreator.currentScriptNodeEl().setSemanticScope(scope);
            break;
        case DomType::ScriptFunctionExpression: {
            // Put the body's scope into the function expression: function expressions will contain
            // their parents scope instead of their own without this
            auto element = m_domCreator.currentScriptNodeEl().value;
            auto scriptElementVariant = std::get_if<ScriptElementVariant>(&element);
            if (!scriptElementVariant || !scriptElementVariant->data())
                break;
            scriptElementVariant->visit([](auto &&e) {
                using U = std::remove_cv_t<std::remove_reference_t<decltype(e)>>;
                if (e->kind() != DomType::ScriptFunctionExpression)
                    return;

                if constexpr (std::is_same_v<U,
                                             ScriptElement::PointerType<
                                                     ScriptElements::GenericScriptElement>>) {
                    if (auto bodyPtr = e->elementChild(Fields::body)) {
                        const auto bodyScope = bodyPtr.base()->semanticScope();
                        e->setSemanticScope(bodyScope);
                    }
                }
            });
            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.
                        const bool usePropertyDefinitionScopeInsteadOfTheBindingScope =
                                QQmlSA::isFunctionScope(scope->scopeType())
                                && scope->parentScope()
                                && scope->parentScope()->scopeType() == QQmlSA::ScopeType::QMLScope;
                        e.setSemanticScope(usePropertyDefinitionScopeInsteadOfTheBindingScope
                                                   ? scope->parentScope()
                                                   : scope);
                    }
                },
                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()
{
}

#define X(name)                                                                              \
    bool QQmlDomAstCreator::visit(AST::name *ast)                                            \
    {                                                                                        \
        return QQmlDomAstCreatorBase::visit(ast) && visitWithCustomListIteration(ast, this); \
    }
QQmlJSASTClassListToVisit
#undef X

} // end namespace Dom
} // end namespace QQmlJS

#undef Q_SCRIPTELEMENT_DISABLE
#undef Q_SCRIPTELEMENT_EXIT_IF

QT_END_NAMESPACE
