f31522130f
Build system (5 gaps hardened): - COOKBOOK_OFFLINE defaults to true (fork-mode) - normalize_patch handles diff -ruN format - New 'repo validate-patches' command (25/25 relibc patches) - 14 patched Qt/Wayland/display recipes added to protected list - relibc archive regenerated with current patch chain Boot fixes (fixable): - Full ISO EFI partition: 16 MiB → 1 MiB (matches mini, BIOS hardcoded 2 MiB offset) - D-Bus system bus: absolute /usr/bin/dbus-daemon path (was skipped) - redbear-sessiond: absolute /usr/bin/redbear-sessiond path (was skipped) - daemon framework: silenced spurious INIT_NOTIFY warnings for oneshot_async services (P0-daemon-silence-init-notify.patch) - udev-shim: demoted INIT_NOTIFY warning to INFO (expected for oneshot_async) - relibc: comprehensive named semaphores (sem_open/close/unlink) replacing upstream todo!() stubs - greeterd: Wayland socket timeout 15s → 30s (compositor DRM wait) - greeter-ui: built and linked (header guard unification, sem_compat stubs removed) - mc: un-ignored in both configs, fixed glib/libiconv/pcre2 transitive deps - greeter config: removed stale keymapd dependency from display/greeter services - prefix toolchain: relibc headers synced, _RELIBC_STDLIB_H guard unified Unfixable (diagnosed, upstream): - i2c-hidd: abort on no-I2C-hardware (QEMU) — process::exit → relibc abort - kded6/greeter-ui: page fault 0x8 — Qt library null deref - Thread panics fd != -1 — Rust std library on Redox - DHCP timeout / eth0 MAC — QEMU user-mode networking - hwrngd/thermald — no hardware RNG/thermal in VM - live preload allocation — BIOS memory fragmentation, continues on demand
3440 lines
124 KiB
C++
3440 lines
124 KiB
C++
// 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 "qqmldomerrormessage_p.h"
|
|
#include "qqmldomastdumper_p.h"
|
|
#include "qqmldomastcreator_p.h"
|
|
#include "qqmldom_utils_p.h"
|
|
|
|
#include <QtQml/private/qqmljsast_p.h>
|
|
#include <QtQmlCompiler/private/qqmljsutils_p.h>
|
|
|
|
#include <QtCore/QDir>
|
|
#include <QtCore/QFileInfo>
|
|
#include <QtCore/QScopeGuard>
|
|
#include <QtCore/QLoggingCategory>
|
|
|
|
#include <memory>
|
|
#include <optional>
|
|
#include <type_traits>
|
|
#include <variant>
|
|
#include <vector>
|
|
|
|
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;
|
|
|
|
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 ¶meterQList = 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()) {
|
|
qmlFile.addError(std::move(
|
|
astParseErrors()
|
|
.warning(tr("id attributes should only be a lower case letter "
|
|
"followed by letters, numbers or underscore, not %1")
|
|
.arg(iExp->name))
|
|
.withPath(pathFromOwner)));
|
|
}
|
|
} else {
|
|
pathFromOwner =
|
|
current<QmlObject>().addBinding(bindingV, AddOption::KeepExisting, &bindingPtr);
|
|
Q_ASSERT_X(bindingPtr, className, "binding could not be retrieved");
|
|
qmlFile.addError(std::move(
|
|
astParseErrors()
|
|
.warning(tr("id attributes should only be a lower case letter "
|
|
"followed by letters, numbers or underscore, not %1 "
|
|
"%2, assuming they refer to a property")
|
|
.arg(script->code(), script->astRelocatableDump()))
|
|
.withPath(pathFromOwner)));
|
|
}
|
|
} else {
|
|
pathFromOwner =
|
|
current<QmlObject>().addBinding(bindingV, AddOption::KeepExisting, &bindingPtr);
|
|
QmlStackElement &containingObjectEl = currentEl<QmlObject>();
|
|
// remove the containingObjectEl.path prefix from pathFromOwner
|
|
Path pathFromContainingObject = pathFromOwner.mid(containingObjectEl.path.length());
|
|
auto bindingFileLocation =
|
|
FileLocations::ensure(containingObjectEl.fileLocations, pathFromContainingObject);
|
|
FileLocations::addRegion(bindingFileLocation, IdentifierRegion,
|
|
el->qualifiedId->identifierToken);
|
|
FileLocations::addRegion(bindingFileLocation, ColonTokenRegion, el->colonToken);
|
|
|
|
setBindingIdentifiers(pathFromOwner, el->qualifiedId, bindingPtr);
|
|
|
|
Q_ASSERT_X(bindingPtr, className, "binding could not be retrieved");
|
|
}
|
|
if (bindingPtr)
|
|
pushEl(pathFromOwner, *bindingPtr, el);
|
|
else if (idPtr)
|
|
pushEl(pathFromOwner, *idPtr, el);
|
|
else
|
|
Q_UNREACHABLE();
|
|
loadAnnotations(el);
|
|
// avoid duplicate colon location for id?
|
|
FileLocations::addRegion(nodeStack.last().fileLocations, ColonTokenRegion, el->colonToken);
|
|
return true;
|
|
}
|
|
|
|
void 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> ¤t)
|
|
{
|
|
if (pe->equalToken.isValid())
|
|
current->addLocation(FileLocationRegion::EqualTokenRegion, pe->equalToken);
|
|
|
|
if (pe->identifierToken.isValid() && !pe->bindingIdentifier.isEmpty()) {
|
|
auto identifier =
|
|
std::make_shared<ScriptElements::IdentifierExpression>(pe->identifierToken);
|
|
identifier->setName(pe->bindingIdentifier);
|
|
current->insertChild(Fields::identifier, ScriptElementVariant::fromElement(identifier));
|
|
}
|
|
if (pe->initializer) {
|
|
Q_SCRIPTELEMENT_EXIT_IF(!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> ¤tExpression,
|
|
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> ¤tTemplate,
|
|
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> ¤tTemplate,
|
|
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(const QQmlJSScope::Ptr ¤t,
|
|
MutableDomItem &qmlFile,
|
|
QQmlJSLogger *logger,
|
|
QQmlJSImporter *importer)
|
|
: m_root(current),
|
|
m_logger(logger),
|
|
m_importer(importer),
|
|
m_implicitImportDirectory(QQmlJSImportVisitor::implicitImportDirectory(
|
|
m_logger->filePath(), m_importer->resourceFileMapper())),
|
|
m_scopeCreator(m_root, m_importer, m_logger, m_implicitImportDirectory,
|
|
qmldirFilesFrom(qmlFile)),
|
|
m_domCreator(qmlFile)
|
|
{
|
|
}
|
|
|
|
#define X(name) \
|
|
bool QQmlDomAstCreatorWithQQmlJSScope::visit(name *node) \
|
|
{ \
|
|
return visitT(node); \
|
|
} \
|
|
void QQmlDomAstCreatorWithQQmlJSScope::endVisit(name *node) \
|
|
{ \
|
|
endVisitT(node); \
|
|
}
|
|
QQmlJSASTClassListToVisit
|
|
#undef X
|
|
|
|
void QQmlDomAstCreatorWithQQmlJSScope::setScopeInDomAfterEndvisit()
|
|
{
|
|
const QQmlJSScope::ConstPtr scope = m_scopeCreator.m_currentScope;
|
|
if (!m_domCreator.scriptNodeStack.isEmpty()) {
|
|
auto topOfStack = m_domCreator.currentScriptNodeEl();
|
|
switch (topOfStack.kind) {
|
|
case DomType::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
|