aboutsummaryrefslogtreecommitdiffstats
path: root/tests/auto/qmldom/domitem/tst_qmldomitem.h
diff options
context:
space:
mode:
Diffstat (limited to 'tests/auto/qmldom/domitem/tst_qmldomitem.h')
-rw-r--r--tests/auto/qmldom/domitem/tst_qmldomitem.h2934
1 files changed, 2890 insertions, 44 deletions
diff --git a/tests/auto/qmldom/domitem/tst_qmldomitem.h b/tests/auto/qmldom/domitem/tst_qmldomitem.h
index 433a92a2b2..0c1ed3b59f 100644
--- a/tests/auto/qmldom/domitem/tst_qmldomitem.h
+++ b/tests/auto/qmldom/domitem/tst_qmldomitem.h
@@ -1,5 +1,5 @@
// Copyright (C) 2020 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
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
#ifndef TST_QMLDOMITEM_H
#define TST_QMLDOMITEM_H
@@ -9,6 +9,7 @@
#include <QtQmlDom/private/qqmldommock_p.h>
#include <QtQmlDom/private/qqmldomcompare_p.h>
#include <QtQmlDom/private/qqmldomfieldfilter_p.h>
+#include <QtQmlDom/private/qqmldomscriptelements_p.h>
#include <QtTest/QtTest>
#include <QtCore/QCborValue>
@@ -16,14 +17,18 @@
#include <QtCore/QLibraryInfo>
#include <QtCore/QFileInfo>
+#include <deque>
#include <memory>
+#include <utility>
+#include <variant>
+#include <vector>
QT_BEGIN_NAMESPACE
namespace QQmlJS {
namespace Dom {
-inline DomItem wrapInt(DomItem &self, const PathEls::PathComponent &p, const int &i)
+inline DomItem wrapInt(const DomItem &self, const PathEls::PathComponent &p, const int &i)
{
return self.subDataItem(p, i);
}
@@ -51,8 +56,9 @@ private slots:
qmltypeDirs = QStringList({ baseDir, QLibraryInfo::path(QLibraryInfo::QmlImportsPath) });
universePtr =
std::shared_ptr<DomUniverse>(new DomUniverse(QStringLiteral(u"dummyUniverse")));
- envPtr = std::shared_ptr<DomEnvironment>(new DomEnvironment(
- QStringList(), DomEnvironment::Option::SingleThreaded, universePtr));
+ envPtr = std::shared_ptr<DomEnvironment>(
+ new DomEnvironment(QStringList(), DomEnvironment::Option::SingleThreaded,
+ DomCreationOption::None, universePtr));
env = DomItem(envPtr);
testOwnerPtr = std::shared_ptr<MockOwner>(new MockOwner(
Path::Root(u"env").field(u"testOwner"), 0,
@@ -363,7 +369,7 @@ private slots:
auto tOwner3 = tOwner.path(u"$env.testOwner");
QCOMPARE(tOwner3.internalKind(), DomType::MockOwner);
QList<qint64> values;
- tOwner.visitTree(Path(), [&values](Path p, DomItem i, bool) {
+ tOwner.visitTree(Path(), [&values](const Path &p, DomItem i, bool) {
if (i.pathFromOwner() != p)
myErrors()
.error(QStringLiteral(u"unexpected path %1 %2")
@@ -403,18 +409,15 @@ private slots:
qmltypeDirs,
QQmlJS::Dom::DomEnvironment::Option::SingleThreaded
| QQmlJS::Dom::DomEnvironment::Option::NoDependencies,
- univPtr));
+ DomCreationOption::None, univPtr));
QQmlJS::Dom::DomItem env(envPtr);
QVERIFY(env);
QString testFile1 = baseDir + QLatin1String("/test1.qml");
DomItem tFile;
- // env.loadBuiltins();
- env.loadFile(
- testFile1, QString(),
- [&tFile](Path, const DomItem &, const DomItem &newIt) { tFile = newIt; },
- LoadOption::DefaultLoad);
- env.loadFile(baseDir, QString(), {}, LoadOption::DefaultLoad);
- env.loadPendingDependencies();
+ envPtr->loadFile(FileToLoad::fromFileSystem(envPtr, testFile1),
+ [&tFile](Path, const DomItem &, const DomItem &newIt) { tFile = newIt; });
+ envPtr->loadFile(FileToLoad::fromFileSystem(envPtr, baseDir), {});
+ envPtr->loadPendingDependencies();
QVERIFY(tFile);
tFile = tFile.field(Fields::currentItem);
@@ -424,7 +427,7 @@ private slots:
DomItem obj1 = comp1.field(Fields::objects).index(0);
QVERIFY(obj1);
- tFile.visitTree(Path(), [&tFile](Path p, DomItem i, bool) {
+ tFile.visitTree(Path(), [&tFile](const Path &p, DomItem i, bool) {
if (!(i == i.path(i.canonicalPath()))) {
DomItem i2 = i.path(i.canonicalPath());
qDebug() << p << i.canonicalPath() << i.internalKindStr() << i2.internalKindStr()
@@ -489,18 +492,16 @@ private slots:
auto univPtr = std::shared_ptr<QQmlJS::Dom::DomUniverse>(
new QQmlJS::Dom::DomUniverse(QLatin1String("univ1")));
auto envPtr = std::shared_ptr<QQmlJS::Dom::DomEnvironment>(new QQmlJS::Dom::DomEnvironment(
- qmltypeDirs, QQmlJS::Dom::DomEnvironment::Option::SingleThreaded, univPtr));
+ qmltypeDirs, QQmlJS::Dom::DomEnvironment::Option::SingleThreaded, {}, univPtr));
QQmlJS::Dom::DomItem env(envPtr);
QVERIFY(env);
QString testFile1 = baseDir + QLatin1String("/test1.qml");
DomItem tFile;
- env.loadBuiltins();
- env.loadFile(
- testFile1, QString(),
- [&tFile](Path, const DomItem &, const DomItem &newIt) { tFile = newIt; },
- LoadOption::DefaultLoad);
- env.loadFile(baseDir, QString(), {}, LoadOption::DefaultLoad);
- env.loadPendingDependencies();
+ envPtr->loadBuiltins();
+ envPtr->loadFile(FileToLoad::fromFileSystem(envPtr, testFile1),
+ [&tFile](Path, const DomItem &, const DomItem &newIt) { tFile = newIt; });
+ envPtr->loadFile(FileToLoad::fromFileSystem(envPtr, baseDir), {});
+ envPtr->loadPendingDependencies();
QVERIFY(tFile);
tFile = tFile.field(Fields::currentItem);
@@ -531,13 +532,13 @@ private slots:
QList<DomItem> rects;
obj1.resolve(
Path::Current(PathCurrent::Lookup).field(Fields::type).key(u"Rectangle"_s),
- [&rects](Path, DomItem &el) {
+ [&rects](Path, const DomItem &el) {
rects.append(el);
return true;
},
{});
QVERIFY(rects.size() == 1);
- for (DomItem &el : rects) {
+ for (const DomItem &el : rects) {
QCOMPARE(rect.first(), el);
}
}
@@ -548,6 +549,13 @@ private slots:
DomItem f2 = env.path(p2);
QVERIFY2(f2, "Directory dependencies did not load MySingleton.qml");
}
+ {
+ QString fPath = tFile.canonicalFilePath();
+ QString fPath2 = fPath.mid(0, fPath.lastIndexOf(u'/')) % u"/ImportMeImplicitly.ui.qml";
+ Path p2 = Paths::qmlFileObjectPath(fPath2);
+ DomItem f2 = env.path(p2);
+ QVERIFY2(f2, "Directory dependencies did not load .ui.qml file!");
+ }
}
void testImports()
@@ -558,17 +566,17 @@ private slots:
using namespace Qt::StringLiterals;
QString testFile1 = baseDir + QLatin1String("/TestImports.qml");
- DomItem env = DomEnvironment::create(
+ auto envPtr = DomEnvironment::create(
QStringList(),
QQmlJS::Dom::DomEnvironment::Option::SingleThreaded
| QQmlJS::Dom::DomEnvironment::Option::NoDependencies);
DomItem tFile;
- env.loadFile(
- testFile1, QString(),
- [&tFile](Path, DomItem &, DomItem &newIt) { tFile = newIt.fileObject(); },
- LoadOption::DefaultLoad);
- env.loadPendingDependencies();
+ envPtr->loadFile(FileToLoad::fromFileSystem(envPtr, testFile1),
+ [&tFile](Path, const DomItem &, const DomItem &newIt) {
+ tFile = newIt.fileObject();
+ });
+ envPtr->loadPendingDependencies();
QVERIFY(tFile);
QList<QmlUri> importedModules;
@@ -601,17 +609,15 @@ private slots:
{
QString testFile = baseDir + QLatin1String("/test1.qml");
- DomItem env = DomEnvironment::create(
+ auto envPtr = DomEnvironment::create(
qmltypeDirs,
QQmlJS::Dom::DomEnvironment::Option::SingleThreaded
| QQmlJS::Dom::DomEnvironment::Option::NoDependencies);
DomItem tFile; // place where to store the loaded file
- env.loadFile(
- testFile, QString(),
- [&tFile](Path, const DomItem &, const DomItem &newIt) { tFile = newIt; },
- LoadOption::DefaultLoad);
- env.loadPendingDependencies();
+ envPtr->loadFile(FileToLoad::fromFileSystem(envPtr, testFile),
+ [&tFile](Path, const DomItem &, const DomItem &newIt) { tFile = newIt; });
+ envPtr->loadPendingDependencies();
DomItem f = tFile.fileObject();
QString dump1;
f.dump([&dump1](QStringView v) { dump1.append(v); });
@@ -626,6 +632,7 @@ private slots:
if (!diffs.isEmpty())
qDebug() << "testDeepCopy.diffs:" << diffs;
QVERIFY(diffs.isEmpty());
+ DomItem env(envPtr);
DomItem univFile = env.universe().path(f.canonicalPath());
MutableDomItem univFileCopy = univFile.makeCopy();
QStringList univFileDiffs =
@@ -645,11 +652,12 @@ private slots:
void testInMemory()
{
DomItem res = DomItem::fromCode("MyItem{}");
+ QCOMPARE(res.qmlObject(GoTo::Strict), DomItem());
DomItem obj = res.qmlObject(GoTo::MostLikely);
QCOMPARE(obj.name(), u"MyItem");
}
- static void checkAliases(DomItem &qmlObj)
+ static void checkAliases(const DomItem &qmlObj)
{
using namespace Qt::StringLiterals;
@@ -714,7 +722,7 @@ private slots:
}
++i;
}
- for (DomItem obj : qmlObj.children().values()) {
+ for (const DomItem &obj : qmlObj.children().values()) {
if (obj.as<QmlObject>())
checkAliases(obj);
}
@@ -734,22 +742,2860 @@ private slots:
QFETCH(QString, inFile);
QString testFile1 = baseDir + u"/"_s + inFile;
- DomItem env = DomEnvironment::create(
+ auto envPtr = DomEnvironment::create(
QStringList(),
QQmlJS::Dom::DomEnvironment::Option::SingleThreaded
| QQmlJS::Dom::DomEnvironment::Option::NoDependencies);
DomItem tFile;
- env.loadFile(
- testFile1, QString(),
- [&tFile](Path, DomItem &, DomItem &newIt) { tFile = newIt.fileObject(); },
- LoadOption::DefaultLoad);
- env.loadPendingDependencies();
+ envPtr->loadFile(FileToLoad::fromFileSystem(envPtr, testFile1),
+ [&tFile](Path, const DomItem &, const DomItem &newIt) {
+ tFile = newIt.fileObject();
+ });
+ envPtr->loadPendingDependencies();
DomItem rootObj = tFile.qmlObject(GoTo::MostLikely);
checkAliases(rootObj);
}
+ void inlineComponents()
+ {
+ using namespace Qt::StringLiterals;
+
+ QString testFile = baseDir + u"/inlineComponents.qml"_s;
+
+ DomCreationOptions options{ DomCreationOption::WithScriptExpressions };
+ auto envPtr = DomEnvironment::create(
+ QStringList(),
+ QQmlJS::Dom::DomEnvironment::Option::SingleThreaded
+ | QQmlJS::Dom::DomEnvironment::Option::NoDependencies, options);
+
+ DomItem tFile;
+ envPtr->loadFile(FileToLoad::fromFileSystem(envPtr, testFile),
+ [&tFile](Path, const DomItem &, const DomItem &newIt) {
+ tFile = newIt.fileObject();
+ });
+ envPtr->loadPendingDependencies();
+
+ auto rootQmlObject = tFile.rootQmlObject(GoTo::MostLikely);
+
+ // check if the lookup can find the inline components correctly, to see if the
+ // visitScopeChain also visit them.
+ auto ic3 = rootQmlObject.lookup("IC3", LookupType::Type, LookupOption::Normal,
+ [](const ErrorMessage &) {});
+
+ QCOMPARE(ic3.size(), 1);
+ QCOMPARE(ic3.front().name(), "inlineComponents.IC3");
+ QCOMPARE(ic3.front().field(Fields::nameIdentifiers).internalKind(), DomType::ScriptType);
+ QCOMPARE(ic3.front()
+ .field(Fields::nameIdentifiers)
+ .field(Fields::typeName)
+ .value()
+ .toString(),
+ u"IC3"_s);
+
+ auto ic1 = rootQmlObject.lookup("IC1", LookupType::Type, LookupOption::Normal,
+ [](const ErrorMessage &) {});
+
+ QCOMPARE(ic1.size(), 1);
+ QCOMPARE(ic1.front().name(), "inlineComponents.IC1");
+ }
+
+ void inlineObject()
+ {
+ using namespace Qt::StringLiterals;
+ QString testFile = baseDir + u"/inlineObject.qml"_s;
+
+ auto envPtr = DomEnvironment::create(
+ QStringList(),
+ QQmlJS::Dom::DomEnvironment::Option::SingleThreaded
+ | QQmlJS::Dom::DomEnvironment::Option::NoDependencies);
+
+ DomItem tFile;
+ envPtr->loadFile(FileToLoad::fromFileSystem(envPtr, testFile),
+ [&tFile](Path, const DomItem &, const DomItem &newIt) {
+ tFile = newIt.fileObject();
+ });
+ envPtr->loadPendingDependencies();
+
+ auto rootQmlObject = tFile.rootQmlObject(GoTo::MostLikely);
+
+ // check that the inline objects have their prototypes set.
+
+ {
+ auto prototypes = rootQmlObject.propertyInfos()
+ .key(u"myItem"_s)
+ .field(Fields::bindings)
+ .index(0)
+ .field(Fields::value)
+ .field(Fields::prototypes);
+ QVERIFY(prototypes.internalKind() != DomType::Empty);
+ QCOMPARE(prototypes.indexes(), 1);
+ QCOMPARE(prototypes.index(0)
+ .field(Fields::referredObjectPath)
+ .as<ConstantData>()
+ ->value()
+ .toString(),
+ u"@lookup.type[\"Item\"]"_s);
+ }
+
+ {
+ auto prototypes2 = rootQmlObject.propertyInfos()
+ .key(u"myItem2"_s)
+ .field(Fields::bindings)
+ .index(0)
+ .field(Fields::value)
+ .field(Fields::prototypes);
+ QVERIFY(prototypes2.internalKind() != DomType::Empty);
+ QCOMPARE(prototypes2.indexes(), 1);
+ QCOMPARE(prototypes2.index(0)
+ .field(Fields::referredObjectPath)
+ .as<ConstantData>()
+ ->value()
+ .toString(),
+ u"@lookup.type[\"IC\"]"_s);
+ }
+ }
+
+ void scopesInDom()
+ {
+ QString fileName = baseDir + u"/checkScopes.qml"_s;
+
+ const QStringList importPaths = {
+ QLibraryInfo::path(QLibraryInfo::QmlImportsPath),
+ };
+
+ DomItem tFile;
+
+ auto envPtr = DomEnvironment::create(
+ importPaths,
+ QQmlJS::Dom::DomEnvironment::Option::SingleThreaded
+ | QQmlJS::Dom::DomEnvironment::Option::NoDependencies,
+ WithSemanticAnalysis);
+
+ envPtr->loadFile(FileToLoad::fromFileSystem(envPtr, fileName),
+ [&tFile](Path, const DomItem &, const DomItem &newIt) {
+ tFile = newIt.fileObject();
+ });
+ envPtr->loadPendingDependencies();
+
+ auto root = tFile.rootQmlObject(GoTo::MostLikely);
+
+ {
+ auto rootQmlObject = root.as<QmlObject>();
+ QVERIFY(rootQmlObject);
+ auto rootScope = rootQmlObject->semanticScope();
+ QVERIFY(rootScope);
+ QVERIFY(rootScope->hasOwnProperty("myInt"));
+ QVERIFY(rootScope->hasOwnProperty("myInt2"));
+ QVERIFY(rootScope->hasOwnPropertyBindings("myInt"));
+ QVERIFY(rootScope->hasOwnPropertyBindings("myInt2"));
+ }
+ }
+
+ void propertyBindings()
+ {
+ using namespace Qt::StringLiterals;
+ QString testFile = baseDir + u"/propertyBindings.qml"_s;
+ DomItem rootQmlObject = rootQmlObjectFromFile(testFile, qmltypeDirs);
+ // check the binding to a and b
+ DomItem a = rootQmlObject.path(".bindings[\"a\"][0].value.scriptElement.value");
+ QCOMPARE(a.value().toDouble(), 42);
+
+ DomItem b = rootQmlObject.path(".bindings[\"b\"][0].value.scriptElement.identifier");
+ QCOMPARE(b.value().toString(), "a");
+
+ DomItem root = rootQmlObject.path(".idStr");
+ QCOMPARE(root.value().toString(), "root");
+
+ DomItem componentIds = rootQmlObject.component().path(".ids");
+ QCOMPARE(componentIds.keys(), QSet<QString>({ "root" }));
+
+ DomItem idScriptExpression =
+ componentIds.key("root").index(0).field(Fields::value).field(Fields::scriptElement);
+ QCOMPARE(idScriptExpression.internalKind(), DomType::ScriptIdentifierExpression);
+ QCOMPARE(idScriptExpression.field(Fields::identifier).value().toString(), "root");
+ }
+
+ void variableDeclarations()
+ {
+ using namespace Qt::StringLiterals;
+ QString testFile = baseDir + u"/variableDeclarations.qml"_s;
+ DomItem rootQmlObject = rootQmlObjectFromFile(testFile, qmltypeDirs);
+ DomItem block = rootQmlObject.path(".methods[\"f\"][0].body.scriptElement");
+
+ // check that variabledeclarationlists and statement lists are correctly collected
+ {
+ // let one = 1, two = 2, three = 3, four = 4, five = 5, six = 6
+ std::vector<QString> data = { u"one"_s, u"two"_s, u"three"_s,
+ u"four"_s, u"five"_s, u"six"_s };
+ DomItem block =
+ rootQmlObject.path(".methods[\"count\"][0].body.scriptElement.statements");
+ DomItem variableDeclaration = block.index(0).field(Fields::declarations);
+ QCOMPARE((size_t)variableDeclaration.indexes(), data.size());
+ for (size_t i = 0; i < data.size(); ++i) {
+ DomItem variableDeclarationEntry = variableDeclaration.index(i);
+ QCOMPARE(variableDeclarationEntry.field(Fields::identifier).value().toString(),
+ data[i]);
+ QCOMPARE((size_t)variableDeclarationEntry.field(Fields::initializer)
+ .value()
+ .toInteger(),
+ i + 1);
+ }
+ // let testMe = 123
+ DomItem testDeclaration = block.index(1).field(Fields::declarations);
+ QCOMPARE(testDeclaration.indexes(), 1);
+ QCOMPARE(testDeclaration.index(0).field(Fields::identifier).value().toString(),
+ u"testMe"_s);
+ QCOMPARE(testDeclaration.index(0).field(Fields::initializer).value().toInteger(),
+ 123ll);
+ }
+
+ // if there is a failure in the following line, then thats because either the
+ // variabledeclarationlist or the statement list was wrongly collected and the
+ // domcreator gave up on creating the Dom (unexpected DomType: DomType::ScriptExpression
+ // means that no Dom was constructed for f's body)
+
+ // This block should have a semantic scope that defines sum and helloWorld
+ auto blockSemanticScope = block.semanticScope();
+ QVERIFY(blockSemanticScope);
+ QVERIFY(blockSemanticScope);
+ QVERIFY(blockSemanticScope->ownJSIdentifier(u"sum"_s));
+ QVERIFY(blockSemanticScope->ownJSIdentifier(u"helloWorld"_s));
+ QVERIFY(blockSemanticScope->ownJSIdentifier(u"a"_s));
+ QVERIFY(blockSemanticScope->ownJSIdentifier(u"b"_s));
+ QVERIFY(blockSemanticScope->ownJSIdentifier(u"aa"_s));
+ QVERIFY(blockSemanticScope->ownJSIdentifier(u"bb"_s));
+ QVERIFY(blockSemanticScope->ownJSIdentifier(u"bool1"_s));
+ QVERIFY(blockSemanticScope->ownJSIdentifier(u"bool2"_s));
+ QVERIFY(blockSemanticScope->ownJSIdentifier(u"nullVar"_s));
+ DomItem statements = block.field(Fields::statements);
+ QCOMPARE(statements.indexes(), 8);
+
+ // avoid that toInteger calls return 0 on error, which sometimes is the correct response.
+ const int invalidEnumValue = 9999;
+
+ {
+ // let sum = 0, helloWorld = "hello"
+ DomItem variableDeclaration = statements.index(0).field(Fields::declarations);
+ QCOMPARE(variableDeclaration.indexes(), 2);
+ DomItem sumInitialization = variableDeclaration.index(0);
+ QEXPECT_FAIL(0, "ScopeTypes not implmented yet, as not needed by qmlls for now!",
+ Continue);
+ QCOMPARE(sumInitialization.field(Fields::scopeType).value().toInteger(invalidEnumValue),
+ ScriptElements::VariableDeclarationEntry::ScopeType::Let);
+ QCOMPARE(sumInitialization.field(Fields::identifier).value().toString(), "sum");
+ QCOMPARE(sumInitialization.field(Fields::initializer)
+ .field(Fields::value)
+ .value()
+ .toDouble(),
+ 0);
+
+ DomItem helloWorldInitialization = variableDeclaration.index(1);
+ QEXPECT_FAIL(0, "ScopeTypes not implmented yet, as not needed by qmlls for now!",
+ Continue);
+ QCOMPARE(helloWorldInitialization.field(Fields::scopeType)
+ .value()
+ .toInteger(invalidEnumValue),
+ ScriptElements::VariableDeclarationEntry::ScopeType::Let);
+ QCOMPARE(helloWorldInitialization.field(Fields::identifier).value().toString(),
+ "helloWorld");
+ QCOMPARE(helloWorldInitialization.field(Fields::initializer)
+ .field(Fields::value)
+ .value()
+ .toString(),
+ "hello");
+ }
+ {
+ // const a = 3
+ DomItem a = statements.index(1).field(Fields::declarations).index(0);
+ QEXPECT_FAIL(0, "ScopeTypes not implmented yet, as not needed by qmlls for now!",
+ Continue);
+ QCOMPARE(a.field(Fields::scopeType).value().toInteger(invalidEnumValue),
+ ScriptElements::VariableDeclarationEntry::ScopeType::Const);
+ QCOMPARE(a.field(Fields::identifier).value().toString(), "a");
+ QCOMPARE(a.field(Fields::initializer).internalKind(), DomType::ScriptLiteral);
+ QCOMPARE(a.field(Fields::initializer).field(Fields::value).value().toInteger(), 3);
+ }
+ {
+ // const b = "patron"
+ DomItem b = statements.index(2).field(Fields::declarations).index(0);
+ QEXPECT_FAIL(0, "ScopeTypes not implmented yet, as not needed by qmlls for now!",
+ Continue);
+ QCOMPARE(b.field(Fields::scopeType).value().toInteger(invalidEnumValue),
+ ScriptElements::VariableDeclarationEntry::ScopeType::Const);
+ QCOMPARE(b.field(Fields::identifier).value().toString(), "b");
+ QCOMPARE(b.field(Fields::initializer).internalKind(), DomType::ScriptLiteral);
+ QCOMPARE(b.field(Fields::initializer).field(Fields::value).value().toString(),
+ "patron");
+ }
+ {
+ // var aa = helloWorld
+ DomItem aa = statements.index(3).field(Fields::declarations).index(0);
+ QEXPECT_FAIL(0, "ScopeTypes not implmented yet, as not needed by qmlls for now!",
+ Continue);
+ QCOMPARE(aa.field(Fields::scopeType).value().toInteger(invalidEnumValue),
+ ScriptElements::VariableDeclarationEntry::ScopeType::Var);
+ QCOMPARE(aa.field(Fields::identifier).value().toString(), "aa");
+ QCOMPARE(aa.field(Fields::initializer).internalKind(),
+ DomType::ScriptIdentifierExpression);
+ QCOMPARE(aa.field(Fields::initializer).field(Fields::identifier).value().toString(),
+ "helloWorld");
+ // var bb = aa
+ DomItem bb = statements.index(3).field(Fields::declarations).index(1);
+ QEXPECT_FAIL(0, "ScopeTypes not implmented yet, as not needed by qmlls for now!",
+ Continue);
+ QCOMPARE(bb.field(Fields::scopeType).value().toInteger(invalidEnumValue),
+ ScriptElements::VariableDeclarationEntry::ScopeType::Var);
+ QCOMPARE(bb.field(Fields::identifier).value().toString(), "bb");
+ QCOMPARE(bb.field(Fields::initializer).internalKind(),
+ DomType::ScriptIdentifierExpression);
+ QCOMPARE(bb.field(Fields::initializer).field(Fields::identifier).value().toString(),
+ "aa");
+ }
+ {
+ // const bool1 = true
+ DomItem bool1 = statements.index(4).field(Fields::declarations).index(0);
+ QEXPECT_FAIL(0, "ScopeTypes not implmented yet, as not needed by qmlls for now!",
+ Continue);
+ QCOMPARE(bool1.field(Fields::scopeType).value().toInteger(invalidEnumValue),
+ ScriptElements::VariableDeclarationEntry::ScopeType::Const);
+ QCOMPARE(bool1.field(Fields::identifier).value().toString(), "bool1");
+ QCOMPARE(bool1.field(Fields::initializer).internalKind(), DomType::ScriptLiteral);
+ QVERIFY(bool1.field(Fields::initializer).field(Fields::value).value().isTrue());
+ }
+ {
+ // let bool2 = false
+ DomItem bool2 = statements.index(5).field(Fields::declarations).index(0);
+ QEXPECT_FAIL(0, "ScopeTypes not implmented yet, as not needed by qmlls for now!",
+ Continue);
+ QCOMPARE(bool2.field(Fields::scopeType).value().toInteger(invalidEnumValue),
+ ScriptElements::VariableDeclarationEntry::ScopeType::Let);
+ QCOMPARE(bool2.field(Fields::identifier).value().toString(), "bool2");
+ QCOMPARE(bool2.field(Fields::initializer).internalKind(), DomType::ScriptLiteral);
+ QVERIFY(bool2.field(Fields::initializer).field(Fields::value).value().isFalse());
+ }
+ {
+ // var nullVar = null
+ DomItem nullVar = statements.index(6).field(Fields::declarations).index(0);
+ QEXPECT_FAIL(0, "ScopeTypes not implmented yet, as not needed by qmlls for now!",
+ Continue);
+ QCOMPARE(nullVar.field(Fields::scopeType).value().toInteger(invalidEnumValue),
+ ScriptElements::VariableDeclarationEntry::ScopeType::Var);
+ QCOMPARE(nullVar.field(Fields::identifier).value().toString(), "nullVar");
+ QCOMPARE(nullVar.field(Fields::initializer).internalKind(), DomType::ScriptLiteral);
+ QVERIFY(nullVar.field(Fields::initializer).field(Fields::value).value().isNull());
+ }
+ }
+
+ void ifStatements()
+ {
+ using namespace Qt::StringLiterals;
+ QString testFile = baseDir + u"/ifStatements.qml"_s;
+ DomItem rootQmlObject = rootQmlObjectFromFile(testFile, qmltypeDirs);
+ DomItem block = rootQmlObject.path(".methods[\"conditional\"][0].body.scriptElement");
+ auto blockSemanticScope = block.semanticScope();
+ QVERIFY(blockSemanticScope);
+
+ DomItem statements = block.field(Fields::statements);
+ QCOMPARE(statements.indexes(), 5);
+
+ // let i = 5
+ DomItem iDeclaration = statements.index(0);
+ QCOMPARE(iDeclaration.internalKind(), DomType::ScriptVariableDeclaration);
+
+ {
+ // if (i)
+ // i = 42
+ DomItem conditional = statements.index(1);
+ DomItem condition = conditional.field(Fields::condition);
+ QCOMPARE(condition.internalKind(), DomType::ScriptIdentifierExpression);
+ QCOMPARE(condition.field(Fields::identifier).value().toString(), u"i"_s);
+
+ DomItem consequence = conditional.field(Fields::consequence);
+ auto nonBlockSemanticScope = consequence.semanticScope();
+ QVERIFY(!nonBlockSemanticScope); // because there is no block
+
+ QCOMPARE(consequence.internalKind(), DomType::ScriptBinaryExpression);
+ QCOMPARE(consequence.field(Fields::left).field(Fields::identifier).value().toString(),
+ u"i"_s);
+ QCOMPARE(consequence.field(Fields::right).field(Fields::value).value().toDouble(), 42);
+
+ QCOMPARE(conditional.field(Fields::alternative).internalKind(), DomType::Empty);
+ }
+ {
+ // if (i == 55)
+ // i = 32
+ // else
+ // i = i - 1
+ DomItem conditional = statements.index(2);
+ DomItem condition = conditional.field(Fields::condition);
+ QCOMPARE(condition.internalKind(), DomType::ScriptBinaryExpression);
+ QCOMPARE(condition.field(Fields::right).field(Fields::value).value().toDouble(), 55);
+
+ DomItem consequence = conditional.field(Fields::consequence);
+ auto nonBlockSemanticScope = consequence.semanticScope();
+ QVERIFY(!nonBlockSemanticScope);
+
+ QCOMPARE(consequence.internalKind(), DomType::ScriptBinaryExpression);
+ QCOMPARE(consequence.field(Fields::left).field(Fields::identifier).value().toString(),
+ u"i"_s);
+ QCOMPARE(consequence.field(Fields::right).field(Fields::value).value().toDouble(), 32);
+
+ DomItem alternative = conditional.field(Fields::alternative);
+ QCOMPARE(alternative.internalKind(), DomType::ScriptBinaryExpression);
+ QCOMPARE(alternative.field(Fields::left).field(Fields::identifier).value().toString(),
+ u"i"_s);
+ QCOMPARE(alternative.field(Fields::right).internalKind(),
+ DomType::ScriptBinaryExpression);
+ }
+ {
+ // if (i == 42) {
+ // i = 111
+ // }
+ DomItem conditional = statements.index(3);
+ DomItem condition = conditional.field(Fields::condition);
+ QCOMPARE(condition.internalKind(), DomType::ScriptBinaryExpression);
+ QCOMPARE(condition.field(Fields::right).field(Fields::value).value().toDouble(), 42);
+
+ DomItem consequence = conditional.field(Fields::consequence);
+ auto blockSemanticScope = consequence.semanticScope();
+ QVERIFY(blockSemanticScope);
+
+ QCOMPARE(consequence.internalKind(), DomType::ScriptBlockStatement);
+ QCOMPARE(consequence.field(Fields::statements).indexes(), 1);
+ DomItem consequence1 = consequence.field(Fields::statements).index(0);
+ QCOMPARE(consequence1.field(Fields::left).field(Fields::identifier).value().toString(),
+ u"i"_s);
+ QCOMPARE(consequence1.field(Fields::right).field(Fields::value).value().toDouble(),
+ 111);
+
+ QCOMPARE(conditional.field(Fields::alternative).internalKind(), DomType::Empty);
+ }
+ {
+ // if (i == 746) {
+ // i = 123
+ // } else {
+ // i = 456
+ // }
+
+ DomItem conditional = statements.index(4);
+ DomItem condition = conditional.field(Fields::condition);
+ QCOMPARE(condition.internalKind(), DomType::ScriptBinaryExpression);
+ QCOMPARE(condition.field(Fields::right).field(Fields::value).value().toDouble(), 746);
+
+ {
+ DomItem consequence = conditional.field(Fields::consequence);
+ QCOMPARE(consequence.internalKind(), DomType::ScriptBlockStatement);
+ QCOMPARE(consequence.field(Fields::statements).indexes(), 1);
+ DomItem consequence1 = consequence.field(Fields::statements).index(0);
+ QCOMPARE(consequence1.field(Fields::left)
+ .field(Fields::identifier)
+ .value()
+ .toString(),
+ u"i"_s);
+ QCOMPARE(consequence1.field(Fields::right).field(Fields::value).value().toDouble(),
+ 123);
+ }
+
+ {
+ DomItem alternative = conditional.field(Fields::alternative);
+ auto blockSemanticScope = alternative.semanticScope();
+ QVERIFY(blockSemanticScope);
+
+ QCOMPARE(alternative.internalKind(), DomType::ScriptBlockStatement);
+ QCOMPARE(alternative.field(Fields::statements).indexes(), 1);
+ DomItem alternative1 = alternative.field(Fields::statements).index(0);
+ QCOMPARE(alternative1.field(Fields::left)
+ .field(Fields::identifier)
+ .value()
+ .toString(),
+ u"i"_s);
+ QCOMPARE(alternative1.field(Fields::right).field(Fields::value).value().toDouble(),
+ 456);
+ }
+ }
+ }
+
+ void returnStatement()
+ {
+ using namespace Qt::StringLiterals;
+ QString testFile = baseDir + u"/returnStatements.qml"_s;
+ DomItem rootQmlObject = rootQmlObjectFromFile(testFile, qmltypeDirs);
+ DomItem block = rootQmlObject.path(".methods[\"returningFunction\"][0].body.scriptElement");
+ QCOMPARE(block.internalKind(), DomType::ScriptBlockStatement);
+ QCOMPARE(block.field(Fields::statements).indexes(), 1);
+ DomItem conditional = block.field(Fields::statements).index(0);
+ DomItem consequence = conditional.field(Fields::consequence);
+ QCOMPARE(consequence.internalKind(), DomType::ScriptReturnStatement);
+ {
+ DomItem returnValue = consequence.field(Fields::expression);
+ QCOMPARE(returnValue.internalKind(), DomType::ScriptLiteral);
+ QCOMPARE(returnValue.field(Fields::value).value().toDouble(), 123);
+ }
+ DomItem alternative = conditional.field(Fields::alternative);
+ QCOMPARE(alternative.internalKind(), DomType::ScriptReturnStatement);
+ {
+ DomItem returnValue = alternative.field(Fields::expression);
+ QCOMPARE(returnValue.internalKind(), DomType::ScriptBinaryExpression);
+ QCOMPARE(returnValue.field(Fields::left).field(Fields::value).value().toDouble(), 1);
+ QCOMPARE(returnValue.field(Fields::right).field(Fields::value).value().toDouble(), 2);
+ }
+ }
+
+ void forStatements()
+ {
+ using namespace Qt::StringLiterals;
+ QString testFile = baseDir + u"/forStatements.qml"_s;
+ DomItem rootQmlObject = rootQmlObjectFromFile(testFile, qmltypeDirs);
+ DomItem block = rootQmlObject.path(".methods[\"f\"][0].body.scriptElement");
+ DomItem statements = block.field(Fields::statements);
+ DomItem forLoop = statements.index(1);
+ {
+ // for ( >> let i = 0 << ; i < 100; i = i + 1) {
+ DomItem declarationList =
+ forLoop.field(Fields::declarations).field(Fields::declarations);
+ QCOMPARE(declarationList.internalKind(), DomType::List);
+
+ QCOMPARE(declarationList.indexes(), 1);
+ DomItem declaration = declarationList.index(0);
+
+ QCOMPARE(declaration.internalKind(), DomType::ScriptVariableDeclarationEntry);
+ QCOMPARE(declaration.field(Fields::initializer).internalKind(), DomType::ScriptLiteral);
+
+ QCOMPARE(declaration.field(Fields::identifier).value().toString(), "i");
+ QCOMPARE(declaration.field(Fields::initializer).field(Fields::value).value().toDouble(),
+ 0);
+ }
+ {
+ // for ( let i = 0; >> i < 100 <<; i = i + 1) {
+ DomItem condition = forLoop.field(Fields::condition);
+ QCOMPARE(condition.internalKind(), DomType::ScriptBinaryExpression);
+
+ QCOMPARE(condition.field(Fields::left).internalKind(),
+ DomType::ScriptIdentifierExpression);
+ QCOMPARE(condition.field(Fields::left).field(Fields::identifier).value().toString(),
+ "i");
+
+ QCOMPARE(condition.field(Fields::right).internalKind(), DomType::ScriptLiteral);
+ QCOMPARE(condition.field(Fields::right).field(Fields::value).value().toDouble(), 100);
+ }
+ {
+ // for ( let i = 0; i < 100; >> i = i + 1 << ) {
+ DomItem expression = forLoop.field(Fields::expression);
+ QCOMPARE(expression.internalKind(), DomType::ScriptBinaryExpression);
+ DomItem left = expression.field(Fields::left);
+ QCOMPARE(left.internalKind(), DomType::ScriptIdentifierExpression);
+ QCOMPARE(left.field(Fields::identifier).value().toString(), "i");
+
+ // for ( let i = 0; i < 100; i = >> i + 1 << ) {
+ DomItem right = expression.field(Fields::right);
+ QCOMPARE(right.internalKind(), DomType::ScriptBinaryExpression);
+ DomItem left2 = right.field(Fields::left);
+ QCOMPARE(left2.internalKind(), DomType::ScriptIdentifierExpression);
+ QCOMPARE(left2.field(Fields::identifier).value().toString(), "i");
+
+ DomItem right2 = right.field(Fields::right);
+ QCOMPARE(right2.internalKind(), DomType::ScriptLiteral);
+ QCOMPARE(right2.field(Fields::value).value().toDouble(), 1);
+ }
+ {
+ // test the body of the for-loop
+ DomItem body = forLoop.field(Fields::body);
+ QCOMPARE(body.internalKind(), DomType::ScriptBlockStatement);
+ auto blockSemanticScope = body.semanticScope();
+ QVERIFY(blockSemanticScope);
+
+ DomItem statementList = body.field(Fields::statements);
+ QCOMPARE(statementList.indexes(), 2);
+ {
+ // >> sum = sum + 1 <<
+ DomItem binaryExpression = statementList.index(0);
+ QCOMPARE(binaryExpression.internalKind(), DomType::ScriptBinaryExpression);
+
+ DomItem left = binaryExpression.field(Fields::left);
+ QCOMPARE(left.internalKind(), DomType::ScriptIdentifierExpression);
+ QCOMPARE(left.field(Fields::identifier).value().toString(), "sum");
+
+ // sum = >> sum + 1 <<
+ DomItem right = binaryExpression.field(Fields::right);
+ QCOMPARE(right.internalKind(), DomType::ScriptBinaryExpression);
+
+ DomItem left2 = right.field(Fields::left);
+ QCOMPARE(left2.internalKind(), DomType::ScriptIdentifierExpression);
+ QCOMPARE(left2.field(Fields::identifier).value().toString(), "sum");
+
+ DomItem right2 = right.field(Fields::right);
+ QCOMPARE(right2.internalKind(), DomType::ScriptLiteral);
+ QCOMPARE(right2.field(Fields::value).value().toDouble(), 1);
+ }
+
+ {
+ // >> for (;;) <<
+ // i = 42
+ DomItem innerForLoop = statementList.index(1);
+ QCOMPARE(innerForLoop.internalKind(), DomType::ScriptForStatement);
+ QCOMPARE(innerForLoop.field(Fields::declarations).indexes(), 0);
+ QVERIFY(!innerForLoop.field(Fields::initializer));
+ QVERIFY(!innerForLoop.field(Fields::condition));
+ QVERIFY(!innerForLoop.field(Fields::expression));
+ QVERIFY(innerForLoop.field(Fields::body));
+
+ // for (;;)
+ // >> i = 42 <<
+ DomItem expression = innerForLoop.field(Fields::body);
+ QCOMPARE(expression.internalKind(), DomType::ScriptBinaryExpression);
+
+ DomItem left = expression.field(Fields::left);
+ QCOMPARE(left.internalKind(), DomType::ScriptIdentifierExpression);
+ QCOMPARE(left.field(Fields::identifier).value().toString(), "i");
+
+ DomItem right = expression.field(Fields::right);
+ QCOMPARE(right.internalKind(), DomType::ScriptLiteral);
+ QCOMPARE(right.field(Fields::value).value().toDouble(), 42);
+ }
+ }
+ }
+
+ void nullStatements()
+ {
+ using namespace Qt::StringLiterals;
+ QString testFile = baseDir + u"/nullStatements.qml"_s;
+ DomItem rootQmlObject = rootQmlObjectFromFile(testFile, qmltypeDirs);
+ DomItem block = rootQmlObject.methods()
+ .key(u"testForNull"_s)
+ .index(0)
+ .field(Fields::body)
+ .field(Fields::scriptElement);
+
+ QVERIFY(block);
+ // First for
+ DomItem statements = block.field(Fields::statements);
+ QCOMPARE(statements.internalKind(), DomType::List);
+ QVERIFY(statements.index(0).field(Fields::body).field(Fields::statements).internalKind()
+ != DomType::Empty);
+ QCOMPARE(statements.index(0).field(Fields::body).field(Fields::statements).length(), 0);
+
+ // Second for
+ DomItem secondFor = statements.index(1).field(Fields::body);
+ QVERIFY(secondFor.internalKind() == DomType::ScriptIdentifierExpression);
+ QCOMPARE(secondFor.field(Fields::identifier).value().toString(), u"x"_s);
+
+ // Empty block
+ QVERIFY(statements.index(3).field(Fields::statements).internalKind() != DomType::Empty);
+ QCOMPARE(statements.index(3).field(Fields::statements).length(), 0);
+ }
+
+ void deconstruction()
+ {
+ using namespace Qt::StringLiterals;
+ QString testFile = baseDir + u"/callExpressions.qml"_s;
+ DomItem rootQmlObject = rootQmlObjectFromFile(testFile, qmltypeDirs);
+
+ DomItem method = rootQmlObject.field(Fields::methods).key(u"deconstruct"_s).index(0);
+ DomItem statement = method.field(Fields::body)
+ .field(Fields::scriptElement)
+ .field(Fields::statements)
+ .index(0);
+ QCOMPARE(statement.internalKind(), DomType::ScriptVariableDeclaration);
+ QCOMPARE(statement.field(Fields::declarations).indexes(), 3);
+
+ {
+ DomItem entry = statement.field(Fields::declarations).index(0);
+ QVERIFY(!entry.field(Fields::identifier));
+ DomItem deconstructedProperty =
+ entry.field(Fields::bindingElement).field(Fields::properties);
+
+ QCOMPARE(deconstructedProperty.indexes(), 1);
+ QCOMPARE(deconstructedProperty.index(0).field(Fields::name).value().toString(), u"a"_s);
+
+ DomItem initializer = entry.field(Fields::initializer).field(Fields::properties);
+ QCOMPARE(initializer.indexes(), 2);
+
+ DomItem a32 = initializer.index(0);
+ QCOMPARE(a32.field(Fields::initializer).value().toInteger(), 32);
+ QCOMPARE(a32.field(Fields::name).value().toString(), "a");
+
+ DomItem b42 = initializer.index(1);
+ QCOMPARE(b42.field(Fields::initializer).value().toInteger(), 42);
+ QCOMPARE(b42.field(Fields::name).value().toString(), "b");
+ }
+ {
+ DomItem entry = statement.field(Fields::declarations).index(1);
+ QVERIFY(!entry.field(Fields::identifier));
+ DomItem deconstructedProperty =
+ entry.field(Fields::bindingElement).field(Fields::properties);
+
+ QCOMPARE(deconstructedProperty.indexes(), 2);
+ QCOMPARE(deconstructedProperty.index(0).field(Fields::name).value().toString(), u"b"_s);
+ QCOMPARE(deconstructedProperty.index(1).field(Fields::name).value().toString(), u"c"_s);
+
+ DomItem initializer = entry.field(Fields::initializer).field(Fields::properties);
+ QCOMPARE(initializer.indexes(), 2);
+
+ DomItem a32 = initializer.index(0);
+ QCOMPARE(a32.field(Fields::initializer).value().toInteger(), 32);
+ QCOMPARE(a32.field(Fields::name).value().toString(), "b");
+
+ DomItem b42 = initializer.index(1);
+ QCOMPARE(b42.field(Fields::initializer).value().toInteger(), 42);
+ QCOMPARE(b42.field(Fields::name).value().toString(), "c");
+ }
+ {
+ DomItem entry = statement.field(Fields::declarations).index(2);
+ QVERIFY(!entry.field(Fields::identifier));
+ DomItem deconstructedProperty =
+ entry.field(Fields::bindingElement).field(Fields::elements);
+
+ QCOMPARE(deconstructedProperty.indexes(), 3);
+ QCOMPARE(deconstructedProperty.index(0).field(Fields::identifier).value().toString(),
+ u"d"_s);
+ QCOMPARE(deconstructedProperty.index(1).field(Fields::identifier).value().toString(),
+ u"e"_s);
+ QCOMPARE(deconstructedProperty.index(2).field(Fields::identifier).value().toString(),
+ u"f"_s);
+
+ DomItem initializer = entry.field(Fields::initializer).field(Fields::elements);
+ QCOMPARE(initializer.indexes(), 3);
+
+ for (int i = 0; i < initializer.indexes(); ++i) {
+ QCOMPARE(initializer.index(i).field(Fields::initializer).value().toInteger(),
+ (i + 1) * 111);
+ }
+ }
+ {
+ DomItem statement = method.field(Fields::body)
+ .field(Fields::scriptElement)
+ .field(Fields::statements)
+ .index(1);
+ DomItem listWithElision = statement.field(Fields::declarations)
+ .index(0)
+ .field(Fields::initializer)
+ .field(Fields::elements);
+ const int listElements = 6;
+ const int elisionCount = 4;
+ QCOMPARE(listWithElision.indexes(), listElements);
+ for (int i = 0; i < elisionCount; ++i) {
+ QCOMPARE(listWithElision.index(i).internalKind(), DomType::ScriptElision);
+ }
+ QCOMPARE(listWithElision.index(elisionCount).internalKind(), DomType::ScriptArrayEntry);
+ QCOMPARE(listWithElision.index(elisionCount + 1).internalKind(),
+ DomType::ScriptElision);
+ }
+ }
+
+ void callExpressions()
+ {
+ using namespace Qt::StringLiterals;
+ QString testFile = baseDir + u"/callExpressions.qml"_s;
+ DomItem rootQmlObject = rootQmlObjectFromFile(testFile, qmltypeDirs);
+
+ {
+ DomItem p1 = rootQmlObject.path(".bindings[\"p\"][0].value.scriptElement");
+ QCOMPARE(p1.internalKind(), DomType::ScriptCallExpression);
+ QCOMPARE(p1.field(Fields::callee).internalKind(), DomType::ScriptIdentifierExpression);
+ QCOMPARE(p1.field(Fields::callee).field(Fields::identifier).value().toString(), "f");
+ QCOMPARE(p1.field(Fields::arguments).internalKind(), DomType::List);
+ QCOMPARE(p1.field(Fields::arguments).indexes(), 0);
+ }
+
+ {
+ DomItem p2 = rootQmlObject.path(".bindings[\"p2\"][0].value.scriptElement");
+ QCOMPARE(p2.internalKind(), DomType::ScriptCallExpression);
+ QCOMPARE(p2.field(Fields::callee).internalKind(), DomType::ScriptIdentifierExpression);
+ QCOMPARE(p2.field(Fields::callee).field(Fields::identifier).value().toString(), "f");
+
+ DomItem p2List = p2.field(Fields::arguments);
+ QCOMPARE(p2List.indexes(), 20);
+ for (int i = 0; i < p2List.indexes(); ++i) {
+ QCOMPARE(p2List.index(i).internalKind(), DomType::ScriptLiteral);
+ QCOMPARE(p2List.index(i).field(Fields::value).value().toInteger(), i + 1);
+ }
+ }
+
+ {
+ DomItem p3 = rootQmlObject.path(".bindings[\"p3\"][0].value.scriptElement");
+ QCOMPARE(p3.internalKind(), DomType::ScriptCallExpression);
+ QCOMPARE(p3.field(Fields::callee).internalKind(), DomType::ScriptIdentifierExpression);
+ QCOMPARE(p3.field(Fields::callee).field(Fields::identifier).value().toString(), "evil");
+
+ DomItem p3List = p3.field(Fields::arguments);
+ QCOMPARE(p3List.indexes(), 3);
+
+ DomItem firstArg = p3List.index(0);
+ QCOMPARE(firstArg.internalKind(), DomType::ScriptObject);
+
+ {
+ DomItem helloWorld = firstArg.field(Fields::properties).index(0);
+ QCOMPARE(helloWorld.internalKind(), DomType::ScriptProperty);
+ QCOMPARE(helloWorld.field(Fields::initializer).value().toString(), "World");
+ QCOMPARE(helloWorld.field(Fields::name).value().toString(), "hello");
+ }
+
+ {
+ DomItem yyyy = firstArg.field(Fields::properties).index(1);
+ QCOMPARE(yyyy.internalKind(), DomType::ScriptProperty);
+ QCOMPARE(yyyy.field(Fields::initializer).value().toString(), "yyy");
+ QCOMPARE(yyyy.field(Fields::name).value().toString(), "y");
+ }
+
+ DomItem secondArg = p3List.index(1);
+ QCOMPARE(secondArg.internalKind(), DomType::ScriptArray);
+ {
+ for (int i = 0; i < 3; ++i) {
+ DomItem current = secondArg.field(Fields::elements).index(i);
+ QCOMPARE(current.internalKind(), DomType::ScriptArrayEntry);
+ QCOMPARE(current.field(Fields::initializer).value().toInteger(), i + 1);
+ }
+ }
+
+ DomItem thirdArg = p3List.index(2);
+ QCOMPARE(thirdArg.internalKind(), DomType::ScriptObject);
+ {
+ DomItem is = thirdArg.field(Fields::properties).index(0);
+ QCOMPARE(is.internalKind(), DomType::ScriptProperty);
+ QCOMPARE(is.field(Fields::name).value().toString(), "is");
+ DomItem initializer = is.field(Fields::initializer).field(Fields::properties);
+
+ QCOMPARE(initializer.index(0).field(Fields::name).value().toString(), "a");
+ QCOMPARE(initializer.index(0).field(Fields::initializer).value().toInteger(), 111);
+
+ QCOMPARE(initializer.index(1).field(Fields::name).value().toString(), "lot");
+ QCOMPARE(initializer.index(1).field(Fields::initializer).value().toInteger(), 222);
+
+ QCOMPARE(initializer.index(2).field(Fields::name).value().toString(), "of");
+ QCOMPARE(initializer.index(2).field(Fields::initializer).value().toInteger(), 333);
+
+ QCOMPARE(initializer.index(3).field(Fields::name).value().toString(), "fun");
+ QCOMPARE(initializer.index(3).field(Fields::initializer).value().toInteger(), 444);
+
+ QCOMPARE(initializer.index(4).field(Fields::name).value().toString(), "really");
+ QCOMPARE(initializer.index(4)
+ .field(Fields::initializer)
+ .field(Fields::elements)
+ .index(0)
+ .field(Fields::initializer)
+ .value()
+ .toString(),
+ "!");
+ }
+ }
+
+ {
+ DomItem functionFs = rootQmlObject.field(Fields::methods).key(u"f");
+ QCOMPARE(functionFs.indexes(), 1);
+ DomItem functionF = functionFs.index(0);
+
+ QVERIFY(!functionF.semanticScope().isNull());
+ QVERIFY(functionF.semanticScope()->ownJSIdentifier("helloF").has_value());
+ DomItem parameters = functionF.field(Fields::parameters);
+ std::vector<QString> parameterNames = {
+ u"q"_s, u"w"_s, u"e"_s, u"r"_s, u"t"_s, u"y"_s
+ };
+ QCOMPARE(parameterNames.size(), (size_t)parameters.indexes());
+
+ for (size_t i = 0; i < parameterNames.size(); ++i) {
+ DomItem currentParameter = parameters.index(i);
+ QCOMPARE(currentParameter.field(Fields::name).value().toString(),
+ parameterNames[i]);
+ }
+ }
+
+ {
+ DomItem functionFWithDefaults =
+ rootQmlObject.field(Fields::methods).key(u"fWithDefault");
+ QCOMPARE(functionFWithDefaults.indexes(), 1);
+ DomItem functionFWithDefault = functionFWithDefaults.index(0);
+ QVERIFY(!functionFWithDefault.semanticScope().isNull());
+ QVERIFY(functionFWithDefault.semanticScope()->ownJSIdentifier(u"helloFWithDefault"_s));
+ DomItem parameters = functionFWithDefault.field(Fields::parameters);
+
+ const std::vector<QString> parameterNames = { u"q"_s, u"w"_s, u"e"_s,
+ u"r"_s, u"t"_s, u"y"_s };
+ const QSet<QString> parameterWithoutDefault = { u"e"_s, u"t"_s };
+
+ QCOMPARE(parameterNames.size(), (size_t)parameters.indexes());
+
+ for (size_t i = 0; i < parameterNames.size(); ++i) {
+ DomItem currentParameter = parameters.index(i);
+ QCOMPARE(currentParameter.field(Fields::name).value().toString(),
+ parameterNames[i]);
+
+ DomItem scriptElement =
+ currentParameter.field(Fields::value).field(Fields::scriptElement);
+ QCOMPARE(scriptElement.internalKind(), DomType::ScriptFormalParameter);
+ QCOMPARE(scriptElement.field(Fields::identifier).value().toString(),
+ parameterNames[i]);
+ if (!parameterWithoutDefault.contains(parameterNames[i])) {
+ QCOMPARE((size_t)scriptElement.field(Fields::initializer).value().toInteger(),
+ i + 1);
+ }
+ }
+ }
+
+ {
+ // note: no need to check the inside of ScriptObject and ScriptArray as those are
+ // already tested in deconstruction().
+ DomItem method = rootQmlObject.field(Fields::methods).key(u"evil"_s).index(0);
+ QVERIFY(!method.semanticScope().isNull());
+ QVERIFY(method.semanticScope()->ownJSIdentifier("helloEvil").has_value());
+ DomItem parameters = method.field(Fields::parameters);
+ QCOMPARE(parameters.indexes(), 3);
+
+ {
+ DomItem parameter1 =
+ parameters.index(0).field(Fields::value).field(Fields::scriptElement);
+ QCOMPARE(parameter1.internalKind(), DomType::ScriptFormalParameter);
+
+ DomItem objectPattern = parameter1.field(Fields::bindingElement);
+ QCOMPARE(objectPattern.internalKind(), DomType::ScriptObject);
+ QCOMPARE(objectPattern.field(Fields::properties).indexes(), 2);
+ }
+ {
+ DomItem parameter2 =
+ parameters.index(1).field(Fields::value).field(Fields::scriptElement);
+ QCOMPARE(parameter2.internalKind(), DomType::ScriptFormalParameter);
+
+ DomItem objectPattern = parameter2.field(Fields::bindingElement);
+ QCOMPARE(objectPattern.internalKind(), DomType::ScriptArray);
+ QCOMPARE(objectPattern.field(Fields::elements).indexes(), 3);
+ }
+ {
+ DomItem parameter3 =
+ parameters.index(2).field(Fields::value).field(Fields::scriptElement);
+ QCOMPARE(parameter3.internalKind(), DomType::ScriptFormalParameter);
+
+ DomItem objectPattern = parameter3.field(Fields::bindingElement);
+ QCOMPARE(objectPattern.internalKind(), DomType::ScriptObject);
+ QCOMPARE(objectPattern.field(Fields::properties).indexes(), 3);
+
+ DomItem parameter3DefaultValue = parameter3.field(Fields::initializer);
+ QCOMPARE(parameter3DefaultValue.internalKind(), DomType::ScriptObject);
+
+ DomItem objectPatternDefaultValue =
+ parameter3DefaultValue.field(Fields::properties);
+ QCOMPARE(objectPatternDefaultValue.indexes(), 3);
+ }
+ }
+ {
+ DomItem method = rootQmlObject.field(Fields::methods).key(u"marmelade"_s).index(0);
+ QVERIFY(!method.semanticScope().isNull());
+ QVERIFY(method.semanticScope()->ownJSIdentifier(u"helloMarmelade"_s));
+ DomItem parameters = method.field(Fields::parameters);
+ QCOMPARE(parameters.indexes(), 1);
+ {
+ DomItem parameter1 =
+ parameters.index(0).field(Fields::value).field(Fields::scriptElement);
+ QCOMPARE(parameter1.internalKind(), DomType::ScriptFormalParameter);
+ QCOMPARE(parameter1.field(Fields::identifier).value().toString(), "onTheBread");
+ }
+ }
+ {
+ DomItem method = rootQmlObject.field(Fields::methods).key(u"marmelade2"_s).index(0);
+ QVERIFY(!method.semanticScope().isNull());
+ QVERIFY(method.semanticScope()->ownJSIdentifier(u"helloMarmelade2"_s));
+ DomItem parameters = method.field(Fields::parameters);
+ QCOMPARE(parameters.indexes(), 3);
+ {
+ DomItem parameter3 =
+ parameters.index(2).field(Fields::value).field(Fields::scriptElement);
+ QCOMPARE(parameter3.internalKind(), DomType::ScriptFormalParameter);
+ QCOMPARE(parameter3.field(Fields::identifier).value().toString(), "onTheBread");
+ }
+ }
+ {
+ DomItem method = rootQmlObject.field(Fields::methods).key(u"withTypes"_s).index(0);
+ QVERIFY(!method.semanticScope().isNull());
+ QCOMPARE(method.semanticScope()->scopeType(),
+ QQmlJSScope::ScopeType::JSFunctionScope);
+ DomItem parameters = method.field(Fields::parameters);
+ QCOMPARE(parameters.indexes(), 2);
+ QCOMPARE(parameters.index(0)
+ .field(Fields::value)
+ .field(Fields::scriptElement)
+ .field(Fields::type)
+ .field(Fields::typeName)
+ .value()
+ .toString(),
+ "int");
+ QCOMPARE(parameters.index(1)
+ .field(Fields::value)
+ .field(Fields::scriptElement)
+ .field(Fields::type)
+ .field(Fields::typeName)
+ .value()
+ .toString(),
+ "MyType");
+ }
+ {
+ DomItem method = rootQmlObject.field(Fields::methods).key(u"empty"_s).index(0);
+ QVERIFY(!method.semanticScope().isNull());
+ QCOMPARE(method.semanticScope()->scopeType(),
+ QQmlJSScope::ScopeType::JSFunctionScope);
+ }
+ {
+ DomItem method = rootQmlObject.field(Fields::methods).key(u"mySignal"_s).index(0);
+ // signals contain the scope of the QML object owning them
+ QVERIFY(!method.semanticScope().isNull());
+ QCOMPARE(method.semanticScope()->scopeType(), QQmlJSScope::ScopeType::QMLScope);
+ }
+ }
+
+ void fieldMemberExpression()
+ {
+ using namespace Qt::StringLiterals;
+ QString testFile = baseDir + u"/fieldMemberExpression.qml"_s;
+ DomItem rootQmlObject = rootQmlObjectFromFile(testFile, qmltypeDirs);
+
+ {
+ DomItem p1 = rootQmlObject.path(".bindings[\"p1\"][0].value.scriptElement");
+ QCOMPARE(p1.internalKind(), DomType::ScriptIdentifierExpression);
+ QCOMPARE(p1.field(Fields::identifier).value().toString(), "p");
+ }
+
+ {
+ DomItem p1Qualified =
+ rootQmlObject.path(".bindings[\"p1Qualified\"][0].value.scriptElement");
+ fieldMemberExpressionHelper(p1Qualified, QStringList{ u"p"_s });
+ QCOMPARE(p1Qualified.field(Fields::left).internalKind(),
+ DomType::ScriptIdentifierExpression);
+ QCOMPARE(p1Qualified.field(Fields::left).field(Fields::identifier).value().toString(),
+ "root");
+ }
+ {
+ // property var p1Bracket: root["p"]
+ DomItem p1 = rootQmlObject.path(".bindings[\"p1Bracket\"][0].value.scriptElement");
+ QCOMPARE(p1.internalKind(), DomType::ScriptBinaryExpression);
+ QCOMPARE(p1.field(Fields::operation).value().toInteger(),
+ ScriptElements::BinaryExpression::ArrayMemberAccess);
+
+ QCOMPARE(p1.field(Fields::left).internalKind(), DomType::ScriptIdentifierExpression);
+ QCOMPARE(p1.field(Fields::left).field(Fields::identifier).value().toString(), "root");
+
+ QCOMPARE(p1.field(Fields::right).internalKind(), DomType::ScriptLiteral);
+ QCOMPARE(p1.field(Fields::right).field(Fields::value).value().toString(), "p");
+ }
+ {
+ // property var p1Index: root[42]
+ DomItem p1 = rootQmlObject.path(".bindings[\"p1Index\"][0].value.scriptElement");
+ QCOMPARE(p1.internalKind(), DomType::ScriptBinaryExpression);
+ QCOMPARE(p1.field(Fields::operation).value().toInteger(),
+ ScriptElements::BinaryExpression::ArrayMemberAccess);
+
+ QCOMPARE(p1.field(Fields::left).internalKind(), DomType::ScriptIdentifierExpression);
+ QCOMPARE(p1.field(Fields::left).field(Fields::identifier).value().toString(), "root");
+
+ QCOMPARE(p1.field(Fields::right).internalKind(), DomType::ScriptLiteral);
+ QCOMPARE(p1.field(Fields::right).field(Fields::value).value().toInteger(), 42);
+ }
+ {
+ // property var p1Key: root[p]
+ DomItem p1 = rootQmlObject.path(".bindings[\"p1Key\"][0].value.scriptElement");
+ QCOMPARE(p1.internalKind(), DomType::ScriptBinaryExpression);
+ QCOMPARE(p1.field(Fields::operation).value().toInteger(),
+ ScriptElements::BinaryExpression::ArrayMemberAccess);
+
+ QCOMPARE(p1.field(Fields::left).internalKind(), DomType::ScriptIdentifierExpression);
+ QCOMPARE(p1.field(Fields::left).field(Fields::identifier).value().toString(), "root");
+
+ QCOMPARE(p1.field(Fields::right).internalKind(), DomType::ScriptIdentifierExpression);
+ QCOMPARE(p1.field(Fields::right).field(Fields::identifier).value().toString(), "p");
+ }
+ {
+ DomItem p1Qualified =
+ rootQmlObject.path(".bindings[\"p1Qualified\"][0].value.scriptElement");
+ fieldMemberExpressionHelper(p1Qualified, QStringList{ u"p"_s });
+ QCOMPARE(p1Qualified.field(Fields::left).internalKind(),
+ DomType::ScriptIdentifierExpression);
+ QCOMPARE(p1Qualified.field(Fields::left).field(Fields::identifier).value().toString(),
+ "root");
+ }
+
+ {
+ DomItem p3 = rootQmlObject.path(".bindings[\"p3\"][0].value.scriptElement");
+ fieldMemberExpressionHelper(p3, QStringList{ u"property2"_s, u"p"_s });
+ DomItem p3Left = p3.field(Fields::left).field(Fields::left);
+ QCOMPARE(p3Left.internalKind(), DomType::ScriptIdentifierExpression);
+ QCOMPARE(p3Left.field(Fields::identifier).value().toString(), "property1");
+ }
+
+ {
+ DomItem p3Qualified =
+ rootQmlObject.path(".bindings[\"p3Qualified\"][0].value.scriptElement");
+ fieldMemberExpressionHelper(p3Qualified,
+ QStringList{ u"property1"_s, u"property2"_s, u"p"_s });
+ DomItem p3Left =
+ p3Qualified.field(Fields::left).field(Fields::left).field(Fields::left);
+ QCOMPARE(p3Left.internalKind(), DomType::ScriptIdentifierExpression);
+ QCOMPARE(p3Left.field(Fields::identifier).value().toString(), "root");
+ }
+ }
+
+ void switchStatement()
+ {
+ using namespace Qt::StringLiterals;
+ QString testFile = baseDir + u"/switchStatement.qml"_s;
+ DomItem rootQmlObject = rootQmlObjectFromFile(testFile, qmltypeDirs);
+ QVERIFY(rootQmlObject);
+
+ {
+ DomItem statements = rootQmlObject.methods()
+ .key(u"switchStatement")
+ .index(0)
+ .field(Fields::body)
+ .field(Fields::scriptElement)
+ .field(Fields::statements);
+ QVERIFY(statements);
+ QCOMPARE(statements.index(0).internalKind(), DomType::ScriptVariableDeclaration);
+ QCOMPARE(statements.index(1).internalKind(), DomType::ScriptVariableDeclaration);
+
+ DomItem firstSwitch = statements.index(2);
+ QVERIFY(firstSwitch);
+ QCOMPARE(firstSwitch.internalKind(), DomType::ScriptSwitchStatement);
+ QCOMPARE(firstSwitch.field(Fields::caseBlock).internalKind(), DomType::ScriptCaseBlock);
+ QCOMPARE(firstSwitch.field(Fields::expression).internalKind(),
+ DomType::ScriptIdentifierExpression);
+ QCOMPARE(firstSwitch.field(Fields::expression)
+ .field(Fields::identifier)
+ .value()
+ .toString(),
+ u"animals");
+
+ DomItem caseClauses = firstSwitch.field(Fields::caseBlock).field(Fields::caseClauses);
+ QVERIFY(caseClauses);
+ QCOMPARE(caseClauses.index(0).internalKind(), DomType::ScriptCaseClause);
+ QCOMPARE(caseClauses.index(0).field(Fields::expression).internalKind(),
+ DomType::ScriptLiteral);
+ QCOMPARE(caseClauses.index(0).field(Fields::expression).value().toString(), u"cat");
+
+ DomItem innerSwitch = caseClauses.index(0).field(Fields::statements).index(0);
+ QVERIFY(innerSwitch);
+ QCOMPARE(innerSwitch.internalKind(), DomType::ScriptSwitchStatement);
+ QCOMPARE(innerSwitch.field(Fields::expression).internalKind(),
+ DomType::ScriptIdentifierExpression);
+ QCOMPARE(innerSwitch.field(Fields::expression).value().toString(), u"no");
+
+ QCOMPARE(innerSwitch.field(Fields::caseBlock)
+ .field(Fields::caseClauses)
+ .index(0)
+ .internalKind(),
+ DomType::ScriptCaseClause);
+ QCOMPARE(innerSwitch.field(Fields::caseBlock)
+ .field(Fields::caseClauses)
+ .index(0)
+ .field(Fields::expression)
+ .internalKind(),
+ DomType::ScriptLiteral);
+ QCOMPARE(innerSwitch.field(Fields::caseBlock)
+ .field(Fields::caseClauses)
+ .index(0)
+ .field(Fields::expression)
+ .value()
+ .toInteger(),
+ 0);
+ QCOMPARE(innerSwitch.field(Fields::caseBlock)
+ .field(Fields::caseClauses)
+ .index(0)
+ .field(Fields::statements)
+ .index(0)
+ .field(Fields::expression)
+ .value()
+ .toString(),
+ u"patron");
+ QCOMPARE(innerSwitch.field(Fields::caseBlock)
+ .field(Fields::caseClauses)
+ .index(1)
+ .internalKind(),
+ DomType::ScriptCaseClause);
+ QCOMPARE(innerSwitch.field(Fields::caseBlock)
+ .field(Fields::caseClauses)
+ .index(1)
+ .field(Fields::statements)
+ .index(0)
+ .field(Fields::expression)
+ .value()
+ .toString(),
+ u"mafik");
+ QCOMPARE(innerSwitch.field(Fields::caseBlock)
+ .field(Fields::defaultClause)
+ .internalKind(),
+ DomType::ScriptDefaultClause);
+ QCOMPARE(innerSwitch.field(Fields::caseBlock)
+ .field(Fields::defaultClause)
+ .field(Fields::statements)
+ .index(0)
+ .field(Fields::expression)
+ .value()
+ .toString(),
+ u"none");
+
+ // case "dog"
+ DomItem caseDogBlock = firstSwitch.field(Fields::caseBlock)
+ .field(Fields::caseClauses)
+ .index(1)
+ .field(Fields::statements)
+ .index(0);
+ QVERIFY(caseDogBlock);
+ // Check if semantic scope is correctly created for the CaseClause
+ auto blockSemanticScope = caseDogBlock.semanticScope();
+ QVERIFY(blockSemanticScope);
+ QVERIFY(blockSemanticScope->ownJSIdentifier(u"name"_s));
+ QVERIFY(blockSemanticScope->ownJSIdentifier(u"another"_s));
+
+ // Default clause
+ DomItem defaultClause =
+ firstSwitch.field(Fields::caseBlock).field(Fields::defaultClause);
+ QVERIFY(defaultClause);
+ QCOMPARE(defaultClause.internalKind(), DomType::ScriptDefaultClause);
+ QCOMPARE(defaultClause.field(Fields::statements).index(0).internalKind(),
+ DomType::ScriptReturnStatement);
+ QCOMPARE(defaultClause.field(Fields::statements)
+ .index(0)
+ .field(Fields::expression)
+ .internalKind(),
+ DomType::ScriptLiteral);
+ QCOMPARE(defaultClause.field(Fields::statements)
+ .index(0)
+ .field(Fields::expression)
+ .value()
+ .toString(),
+ u"monster");
+
+ // case "moreCases!"
+ const DomItem moreCases = firstSwitch.field(Fields::caseBlock)
+ .field(Fields::moreCaseClauses)
+ .index(0)
+ .field(Fields::statements)
+ .index(0);
+
+ QCOMPARE(moreCases.internalKind(),
+ DomType::ScriptReturnStatement);
+ QCOMPARE(moreCases.field(Fields::expression).value().toString(), u"moreCaseClauses?");
+ }
+ }
+
+ void iterationStatements()
+ {
+ using namespace Qt::StringLiterals;
+ QString testFile = baseDir + u"/iterationStatements.qml"_s;
+ DomItem rootQmlObject = rootQmlObjectFromFile(testFile, qmltypeDirs);
+
+ QVERIFY(rootQmlObject);
+ DomItem methods = rootQmlObject.methods();
+ QVERIFY(methods);
+
+ {
+ // while statement
+ DomItem whileTest = methods.key("whileStatement")
+ .index(0)
+ .field(Fields::body)
+ .field(Fields::scriptElement)
+ .field(Fields::statements)
+ .index(1);
+ QVERIFY(whileTest);
+ QCOMPARE(whileTest.internalKind(), DomType::ScriptWhileStatement);
+ auto whileScope = whileTest.semanticScope();
+ QVERIFY(whileScope);
+ DomItem whileBody = whileTest.field(Fields::body);
+ QVERIFY(whileBody);
+ QCOMPARE(whileBody.internalKind(), DomType::ScriptBlockStatement);
+ auto scope = whileBody.semanticScope();
+ QVERIFY(scope);
+ QCOMPARE(whileBody.field(Fields::statements).index(0).internalKind(),
+ DomType::ScriptBinaryExpression); // i = i - 1
+ QCOMPARE(
+ whileBody.field(Fields::statements).index(0).field(Fields::left).internalKind(),
+ DomType::ScriptIdentifierExpression);
+ QCOMPARE(whileBody.field(Fields::statements)
+ .index(0)
+ .field(Fields::left)
+ .field(Fields::identifier)
+ .value()
+ .toString(),
+ u"i");
+ DomItem whileExpression = whileTest.field(Fields::expression);
+ QVERIFY(whileExpression);
+ QCOMPARE(whileExpression.internalKind(), DomType::ScriptBinaryExpression); // i > 0
+ QCOMPARE(whileExpression.field(Fields::left).internalKind(),
+ DomType::ScriptIdentifierExpression);
+ QCOMPARE(whileExpression.field(Fields::left)
+ .field(Fields::identifier)
+ .value()
+ .toString(),
+ u"i");
+ QCOMPARE(whileExpression.field(Fields::right).internalKind(), DomType::ScriptLiteral);
+ QCOMPARE(whileExpression.field(Fields::right)
+ .field(Fields::identifier)
+ .value()
+ .toInteger(),
+ 0);
+
+ DomItem singleLineWhile = methods.key("whileStatement")
+ .index(0)
+ .field(Fields::body)
+ .field(Fields::scriptElement)
+ .field(Fields::statements)
+ .index(2);
+ QVERIFY(singleLineWhile);
+ QCOMPARE(singleLineWhile.internalKind(), DomType::ScriptWhileStatement);
+ DomItem singleLineWhileBody = singleLineWhile.field(Fields::body);
+ QVERIFY(singleLineWhileBody);
+ QCOMPARE(singleLineWhileBody.internalKind(), DomType::ScriptBinaryExpression);
+ auto singleLineWhileScope = singleLineWhile.semanticScope();
+ QVERIFY(singleLineWhileScope);
+ QVERIFY(singleLineWhileScope->jsIdentifier("i")); // i is in the parent scope
+ }
+
+ {
+ // do-while statement
+ DomItem doWhile = methods.key("doWhile")
+ .index(0)
+ .field(Fields::body)
+ .field(Fields::scriptElement)
+ .field(Fields::statements)
+ .index(1);
+ QVERIFY(doWhile);
+ QCOMPARE(doWhile.internalKind(), DomType::ScriptDoWhileStatement);
+
+ auto doWhileScope = doWhile.semanticScope();
+ QVERIFY(doWhileScope);
+ DomItem doWhileBody = doWhile.field(Fields::body);
+ QVERIFY(doWhileBody);
+ auto doWhileBodyScope = doWhileBody.semanticScope();
+ QVERIFY(doWhileBodyScope);
+ QVERIFY(doWhileBodyScope->ownJSIdentifier("b")); // const b = a
+ QCOMPARE(doWhileBody.internalKind(), DomType::ScriptBlockStatement);
+ QCOMPARE(doWhileBody.field(Fields::statements).index(0).internalKind(),
+ DomType::ScriptVariableDeclaration); // const b = a
+ QCOMPARE(doWhileBody.field(Fields::statements).index(1).internalKind(),
+ DomType::ScriptBinaryExpression); // a = a - 1
+
+ DomItem doWhileExpression = doWhile.field(Fields::expression);
+ QVERIFY(doWhileExpression);
+ QCOMPARE(doWhileExpression.internalKind(), DomType::ScriptBinaryExpression); // a > 0
+ QCOMPARE(doWhileExpression.field(Fields::left).internalKind(),
+ DomType::ScriptIdentifierExpression);
+ QCOMPARE(doWhileExpression.field(Fields::left)
+ .field(Fields::identifier)
+ .value()
+ .toString(),
+ u"a");
+ QCOMPARE(doWhileExpression.field(Fields::right).internalKind(), DomType::ScriptLiteral);
+ QCOMPARE(doWhileExpression.field(Fields::right)
+ .field(Fields::identifier)
+ .value()
+ .toInteger(),
+ 0);
+ DomItem singleDoWhile = methods.key("doWhile")
+ .index(0)
+ .field(Fields::body)
+ .field(Fields::scriptElement)
+ .field(Fields::statements)
+ .index(2);
+ auto singleDoWhileScope = singleDoWhile.semanticScope();
+ QVERIFY(singleDoWhileScope);
+ QVERIFY(singleDoWhileScope->jsIdentifier("a")); // a = a - 1
+ }
+
+ {
+ // for..of
+ DomItem statements = methods.key("forOf")
+ .index(0)
+ .field(Fields::body)
+ .field(Fields::scriptElement)
+ .field(Fields::statements);
+ QVERIFY(statements);
+ QCOMPARE(statements.index(0).internalKind(), DomType::ScriptVariableDeclaration);
+ DomItem outerForEach = statements.index(1);
+ QVERIFY(outerForEach);
+ QCOMPARE(outerForEach.internalKind(), DomType::ScriptForEachStatement);
+ DomItem bindingElements =
+ outerForEach.field(Fields::bindingElement).field(Fields::bindingElement);
+ QVERIFY(bindingElements);
+ QCOMPARE(bindingElements.internalKind(), DomType::ScriptArray);
+ QCOMPARE(bindingElements.field(Fields::elements).length(), 2);
+ QCOMPARE(bindingElements.field(Fields::elements)
+ .index(1)
+ .field(Fields::identifier)
+ .value()
+ .toString(),
+ "b");
+ QCOMPARE(outerForEach.field(Fields::expression).internalKind(),
+ DomType::ScriptIdentifierExpression);
+ QCOMPARE(outerForEach.field(Fields::expression)
+ .field(Fields::identifier)
+ .value()
+ .toString(),
+ u"iterable");
+ DomItem forEachStatements = outerForEach.field(Fields::body).field(Fields::statements);
+ QCOMPARE(forEachStatements.index(0).internalKind(), DomType::ScriptVariableDeclaration);
+ QCOMPARE(forEachStatements.index(1).internalKind(), DomType::ScriptForEachStatement);
+ QCOMPARE(forEachStatements.index(2).internalKind(), DomType::ScriptForEachStatement);
+ QCOMPARE(forEachStatements.index(3).internalKind(), DomType::ScriptForEachStatement);
+ DomItem firstForEach = forEachStatements.index(1);
+ QVERIFY(firstForEach);
+ DomItem bindingElement =
+ firstForEach.field(Fields::bindingElement).field(Fields::bindingElement);
+ QCOMPARE(bindingElement.internalKind(), DomType::ScriptArray);
+
+ QCOMPARE(bindingElement.field(Fields::elements).length(), 4);
+ QCOMPARE(bindingElement.field(Fields::elements)
+ .index(0)
+ .field(Fields::identifier)
+ .field(Fields::identifier)
+ .value()
+ .toString(),
+ "a1");
+ DomItem secondForEach = forEachStatements.index(2);
+ QCOMPARE(secondForEach.field(Fields::bindingElement).internalKind(),
+ DomType::ScriptPattern);
+ QCOMPARE(secondForEach.field(Fields::bindingElement)
+ .field(Fields::identifier)
+ .field(Fields::identifier)
+ .value()
+ .toString(),
+ "k");
+ QCOMPARE(secondForEach.internalKind(), DomType::ScriptForEachStatement);
+ QCOMPARE(secondForEach.field(Fields::expression).internalKind(), DomType::ScriptArray);
+ QVERIFY(secondForEach.field(Fields::body));
+ QCOMPARE(secondForEach.field(Fields::body).internalKind(),
+ DomType::ScriptBlockStatement);
+ DomItem thirdForEach = forEachStatements.index(3);
+ QCOMPARE(thirdForEach.field(Fields::bindingElement).internalKind(),
+ DomType::ScriptIdentifierExpression);
+ QCOMPARE(thirdForEach.field(Fields::bindingElement).value().toString(), "t");
+
+ QCOMPARE(thirdForEach.internalKind(), DomType::ScriptForEachStatement);
+ QCOMPARE(thirdForEach.field(Fields::expression).internalKind(),
+ DomType::ScriptIdentifierExpression);
+ QCOMPARE(thirdForEach.field(Fields::expression)
+ .field(Fields::identifier)
+ .value()
+ .toString(),
+ u"a");
+ QVERIFY(thirdForEach.field(Fields::body));
+ QCOMPARE(thirdForEach.field(Fields::body).internalKind(),
+ DomType::ScriptBlockStatement);
+
+ DomItem forthForEach = forEachStatements.index(3);
+ QVERIFY(forthForEach);
+ auto forthForEachScope = forthForEach.semanticScope();
+ QVERIFY(forthForEachScope);
+ QVERIFY(forthForEachScope->jsIdentifier("t")); // t lives in parent scope
+ }
+
+ {
+ // for..in
+ DomItem statements = methods.key("forIn")
+ .index(0)
+ .field(Fields::body)
+ .field(Fields::scriptElement)
+ .field(Fields::statements);
+ QVERIFY(statements);
+ QCOMPARE(statements.index(0).internalKind(), DomType::ScriptVariableDeclaration);
+ DomItem outerForEach = statements.index(1);
+ QVERIFY(outerForEach);
+ auto outerForEachScope = outerForEach.semanticScope();
+ QVERIFY(outerForEachScope);
+ QVERIFY(outerForEachScope->jsIdentifier("a")); // var [a,b,c,d]
+ QVERIFY(outerForEachScope->jsIdentifier("b"));
+ QVERIFY(outerForEachScope->jsIdentifier("c"));
+ QVERIFY(outerForEachScope->jsIdentifier("d"));
+ QCOMPARE(outerForEach.internalKind(), DomType::ScriptForEachStatement);
+ QCOMPARE(statements.index(1).internalKind(), DomType::ScriptForEachStatement);
+ QCOMPARE(outerForEach.field(Fields::expression)
+ .field(Fields::identifier)
+ .value()
+ .toString(),
+ "enumerable");
+ auto outerForEachBodyScope = outerForEach.field(Fields::body).semanticScope();
+ QVERIFY(outerForEachBodyScope);
+ QVERIFY(outerForEachBodyScope->ownJSIdentifier("t")); // let t
+ DomItem forInStatements = outerForEach.field(Fields::body).field(Fields::statements);
+ QCOMPARE(forInStatements.index(0).internalKind(), DomType::ScriptVariableDeclaration);
+ QCOMPARE(forInStatements.index(1).internalKind(), DomType::ScriptForEachStatement);
+ QCOMPARE(forInStatements.index(2).internalKind(), DomType::ScriptForEachStatement);
+ DomItem firstForEach = forInStatements.index(1);
+ QVERIFY(firstForEach);
+ auto firstForEachScope = firstForEach.semanticScope();
+ QVERIFY(firstForEachScope);
+ QVERIFY(firstForEachScope->jsIdentifier("t"));
+ QCOMPARE(firstForEach.field(Fields::bindingElement).internalKind(),
+ DomType::ScriptIdentifierExpression);
+ QCOMPARE(firstForEach.field(Fields::bindingElement)
+ .field(Fields::identifier)
+ .value()
+ .toString(),
+ "t");
+ QCOMPARE(firstForEach.internalKind(), DomType::ScriptForEachStatement);
+ QCOMPARE(firstForEach.field(Fields::expression).internalKind(),
+ DomType::ScriptIdentifierExpression);
+ QCOMPARE(firstForEach.field(Fields::expression)
+ .field(Fields::identifier)
+ .value()
+ .toString(),
+ "enumerable");
+ QVERIFY(firstForEach.field(Fields::body));
+ QCOMPARE(firstForEach.field(Fields::body).internalKind(),
+ DomType::ScriptBlockStatement);
+
+ DomItem secondForEach = forInStatements.index(2);
+ QVERIFY(secondForEach);
+ auto secondForEachScope = secondForEach.semanticScope();
+ QVERIFY(secondForEachScope);
+ QVERIFY(secondForEachScope->ownJSIdentifier("a1")); // const [a1,,a2,...rest]
+ QVERIFY(secondForEachScope->ownJSIdentifier("a2"));
+ QVERIFY(secondForEachScope->ownJSIdentifier("rest"));
+
+ DomItem bindingElement =
+ secondForEach.field(Fields::bindingElement).field(Fields::bindingElement);
+ QCOMPARE(bindingElement.internalKind(), DomType::ScriptArray);
+ QCOMPARE(bindingElement.field(Fields::elements)
+ .index(3)
+ .field(Fields::identifier)
+ .field(Fields::identifier)
+ .value()
+ .toString(),
+ "rest");
+ QCOMPARE(secondForEach.internalKind(), DomType::ScriptForEachStatement);
+ QCOMPARE(secondForEach.field(Fields::expression).internalKind(),
+ DomType::ScriptBinaryExpression);
+ QVERIFY(secondForEach.field(Fields::body));
+ QCOMPARE(secondForEach.field(Fields::body).internalKind(),
+ DomType::ScriptBlockStatement);
+ DomItem thirdForEach = forInStatements.index(3);
+ QVERIFY(thirdForEach);
+ auto thirdForEachScope = thirdForEach.semanticScope();
+ QVERIFY(thirdForEachScope);
+ QVERIFY(thirdForEachScope->ownJSIdentifier("t"));
+ }
+ }
+
+ void doNotCrashEcmaScriptClass()
+ {
+ using namespace Qt::StringLiterals;
+ QString testFile = baseDir + u"/ecmaScriptClass.qml"_s;
+ DomItem rootQmlObject = rootQmlObjectFromFile(testFile, qmltypeDirs);
+ QVERIFY(rootQmlObject);
+ }
+
+ void bindingAttachedOrGroupedProperties()
+ {
+ using namespace Qt::StringLiterals;
+ QString testFile = baseDir + u"/attachedOrGroupedProperties.qml"_s;
+ DomItem rootQmlObject = rootQmlObjectFromFile(testFile, qmltypeDirs);
+ QVERIFY(rootQmlObject);
+
+ DomItem dotNotation = rootQmlObject.path(u".children[0].bindings[\"grouped.font.family\"][0].bindingIdentifiers");
+ QVERIFY(dotNotation);
+ QCOMPARE(dotNotation.internalKind(), DomType::ScriptBinaryExpression);
+ QCOMPARE(dotNotation.field(Fields::left).internalKind(), DomType::ScriptBinaryExpression);
+ QCOMPARE(dotNotation.field(Fields::right).field(Fields::identifier).value().toString(), u"family");
+ QCOMPARE(dotNotation.field(Fields::right).internalKind(), DomType::ScriptIdentifierExpression);
+ QCOMPARE(dotNotation.field(Fields::left).field(Fields::left).internalKind(), DomType::ScriptIdentifierExpression);
+ QCOMPARE(dotNotation.field(Fields::left).field(Fields::left).field(Fields::identifier).value().toString(), u"grouped");
+ QCOMPARE(dotNotation.field(Fields::left).field(Fields::right).internalKind(), DomType::ScriptIdentifierExpression);
+ QCOMPARE(dotNotation.field(Fields::left).field(Fields::right).field(Fields::identifier).value().toString(), u"font");
+ auto dotNotationScope = dotNotation.semanticScope();
+ QVERIFY(!dotNotationScope);
+
+ DomItem groupNotationChild1 = rootQmlObject.path(u".children[1].children[0]");
+ QVERIFY(groupNotationChild1);
+ QCOMPARE(groupNotationChild1.internalKind(), DomType::QmlObject);
+ QCOMPARE(groupNotationChild1.field(Fields::name).value().toString(), u"myText");
+ auto myTextScope = groupNotationChild1.semanticScope();
+ QVERIFY(myTextScope);
+ QCOMPARE(myTextScope->scopeType(), QQmlJSScope::ScopeType::GroupedPropertyScope);
+ QVERIFY(myTextScope->hasProperty("font"));
+
+ DomItem groupNotationChild2 = groupNotationChild1.path(u".children[0]");
+ QCOMPARE(groupNotationChild2.internalKind(), DomType::QmlObject);
+ QCOMPARE(groupNotationChild2.field(Fields::name).value().toString(), u"font");
+
+ auto fontScope = groupNotationChild2.semanticScope();
+ QVERIFY(fontScope);
+ QCOMPARE(fontScope->scopeType(), QQmlJSScope::ScopeType::GroupedPropertyScope);
+ QVERIFY(fontScope->hasProperty("pixelSize"));
+
+ DomItem pixelSize = groupNotationChild2.path(u".bindings[\"pixelSize\"][0].bindingIdentifiers");
+ QCOMPARE(pixelSize.internalKind(), DomType::ScriptIdentifierExpression);
+ QCOMPARE(pixelSize.field(Fields::identifier).value().toString(), u"pixelSize");
+
+ DomItem attached = rootQmlObject.path(u".bindings[\"Keys.onPressed\"][0].bindingIdentifiers");
+ QVERIFY(attached);
+ QCOMPARE(attached.internalKind(), DomType::ScriptBinaryExpression);
+ QCOMPARE(attached.field(Fields::left).field(Fields::identifier).value().toString(), u"Keys");
+ QCOMPARE(attached.field(Fields::right).field(Fields::identifier).value().toString(), u"onPressed");
+ }
+
+ void enumDeclarations()
+ {
+ using namespace Qt::StringLiterals;
+ QString testFile = baseDir + u"/enumDeclarations.qml"_s;
+ DomItem fileObject = rootQmlObjectFromFile(testFile, qmltypeDirs).fileObject();
+ QVERIFY(fileObject);
+ DomItem enums = fileObject.path(u".components[\"\"][0].enumerations");
+ QVERIFY(enums);
+
+ DomItem catsEnum = enums.key("Cats").index(0);
+ QVERIFY(catsEnum);
+ QCOMPARE(catsEnum.internalKind(), DomType::EnumDecl);
+ QCOMPARE(catsEnum.name(), u"Cats");
+
+ auto values = catsEnum.field(Fields::values);
+ QCOMPARE(values.length(), 3);
+ QCOMPARE(values.index(0).internalKind(), DomType::EnumItem);
+ QCOMPARE(values.index(0).name(), u"Patron");
+ QCOMPARE(values.index(0).field(Fields::value).value().toInteger(), 0);
+ QCOMPARE(values.index(1).internalKind(), DomType::EnumItem);
+ QCOMPARE(values.index(1).name(), u"Mafya");
+ QCOMPARE(values.index(1).field(Fields::value).value().toInteger(), 1);
+ QCOMPARE(values.index(2).internalKind(), DomType::EnumItem);
+ QCOMPARE(values.index(2).name(), u"Kivrik");
+ QCOMPARE(values.index(2).field(Fields::value).value().toInteger(), -1);
+ }
+
+ void tryStatements()
+ {
+ using namespace Qt::StringLiterals;
+ const QString testFile = baseDir + u"/tryStatements.qml"_s;
+ const DomItem root = rootQmlObjectFromFile(testFile, qmltypeDirs);
+ QVERIFY(root);
+ const DomItem statements = root.path(u".methods[\"f\"][0].body.scriptElement.statements");
+ QCOMPARE(statements.indexes(), 3);
+
+ // test the try blocks
+ for (int i = 0; i < 3; ++i) {
+ const DomItem statement = statements.index(i).field(Fields::block);
+ QVERIFY(statement);
+ QCOMPARE(statement.internalKind(), DomType::ScriptBlockStatement);
+ QCOMPARE(statement.field(Fields::statements)
+ .index(0)
+ .field(Fields::identifier)
+ .value()
+ .toString(),
+ u"insideTry"_s);
+ }
+
+ // test the catch blocks
+ for (int i = 0; i < 3; ++i) {
+ const DomItem statement = statements.index(i).field(Fields::catchBlock);
+ if (i == 2) {
+ QVERIFY(!statement); // no catch in last statement
+ continue;
+ }
+
+ QVERIFY(statement);
+ QCOMPARE(statement.internalKind(), DomType::ScriptBlockStatement);
+ QCOMPARE(statement.field(Fields::statements)
+ .index(0)
+ .field(Fields::identifier)
+ .value()
+ .toString(),
+ u"insideCatch"_s);
+
+ const DomItem expression = statements.index(i).field(Fields::catchParameter);
+ QVERIFY(expression);
+ QCOMPARE(expression.field(Fields::identifier)
+ .value()
+ .toString(),
+ u"catchExpression"_s);
+ }
+
+ // test the finally blocks
+ for (int i = 0; i < 3; ++i) {
+ const DomItem statement = statements.index(i).field(Fields::finallyBlock);
+ if (i == 1) {
+ QVERIFY(!statement); // no finally in last statement
+ continue;
+ }
+
+ QVERIFY(statement);
+ QCOMPARE(statement.internalKind(), DomType::ScriptBlockStatement);
+ QCOMPARE(statement.field(Fields::statements)
+ .index(0)
+ .field(Fields::identifier)
+ .value()
+ .toString(),
+ u"insideFinally"_s);
+ }
+ }
+
+ void plainJSDOM_data()
+ {
+ QTest::addColumn<QString>("filename");
+ QTest::addColumn<QString>("content");
+
+ QTest::newRow("simplestJSStatement")
+ << "simplestJSStatement.js" << QString(u"let v=1;\n"_s);
+ QTest::newRow("import")
+ << "import.js"
+ << QString(u".import \"main.js\" as Main\nconsole.log(Main.a);\n"_s);
+ QTest::newRow("simplestJSmodule")
+ << "simplestJSmodule.mjs" << QString(u"export function entry() {}\n"_s);
+ }
+
+ // Verifies that DOM can load .js and .mjs files and
+ // parse / store the content inside the ScriptExpression
+ void plainJSDOM()
+ {
+ using namespace Qt::StringLiterals;
+ QFETCH(QString, filename);
+ QFETCH(QString, content);
+
+ QString testFile = baseDir + "/" + filename;
+ auto dom = parse(testFile, qmltypeDirs);
+ QVERIFY(dom);
+ QCOMPARE(dom.internalKind(), DomType::JsFile);
+ auto filePtr = dom.fileObject().ownerAs<JsFile>();
+ QVERIFY(filePtr && filePtr->isValid());
+ auto exprAsString = dom.field(Fields::expression)
+ .field(Fields::code)
+ .value()
+ .toString();
+ QVERIFY(!exprAsString.isEmpty());
+ exprAsString.replace("\r\n", "\n");
+ QCOMPARE(exprAsString, content);
+ }
+
+private:
+ struct DomItemWithLocation
+ {
+ DomItem item;
+ FileLocations::Tree tree;
+ };
+private slots:
+
+ void fileLocations_data()
+ {
+ QTest::addColumn<QString>("fileName");
+ QDir dir(baseDir);
+ for (const QString &file : dir.entryList(QDir::Files, QDir::Name)) {
+ if (!file.endsWith(".qml"))
+ continue;
+ QTest::addRow("%s", file.toStdString().c_str()) << baseDir + QDir::separator() + file;
+ }
+ }
+
+ /*!
+ \internal
+ Check if finalizeScriptExpression() was called with the correct FileLocations::Tree
+ argument when this test fails.
+ */
+ void fileLocations()
+ {
+ QFETCH(QString, fileName);
+
+ DomItem rootQmlObject = rootQmlObjectFromFile(fileName, qmltypeDirs);
+ std::deque<DomItemWithLocation> queue;
+
+ DomItem fileDomItem = rootQmlObject.containingFile();
+ std::shared_ptr<QmlFile> file = fileDomItem.ownerAs<QmlFile>();
+ QVERIFY(file);
+
+ DomItemWithLocation root{ fileDomItem, file->fileLocationsTree() };
+ queue.push_back(root);
+
+ while (!queue.empty()) {
+ DomItemWithLocation current = queue.front();
+ queue.pop_front();
+
+ auto subEls = current.tree->subItems();
+ for (auto it = subEls.begin(); it != subEls.end(); ++it) {
+ DomItem childItem = current.item.path(it.key());
+ FileLocations::Tree childTree =
+ std::static_pointer_cast<AttachedInfoT<FileLocations>>(it.value());
+ if (!childItem) {
+ const auto attachedInfo = FileLocations::findAttachedInfo(current.item);
+ const DomItem treeItem = current.item.path(attachedInfo.foundTreePath);
+ qDebug() << current.item.internalKindStr()
+ << "has incorrect FileLocations! Make sure that "
+ "finalizeScriptExpression is called with the right arguments. It "
+ "should print out some debugging information with the "
+ "qt.qmldom.astcreator.debug logging rule.";
+ qDebug() << "Current FileLocations has keys:"
+ << treeItem.field(Fields::subItems).keys()
+ << "but current Item of type" << current.item.internalKindStr()
+ << "has fields: " << current.item.fields()
+ << "and keys: " << current.item.keys()
+ << "and indexes: " << current.item.indexes();
+ }
+ QVERIFY(childItem.internalKind() != DomType::Empty);
+ queue.push_back({ childItem, childTree });
+ }
+ }
+ }
+
+private slots:
+ void finalizeScriptExpressions()
+ {
+ QString fileName = baseDir + u"/finalizeScriptExpressions.qml"_s;
+ DomItem rootQmlObject = rootQmlObjectFromFile(fileName, qmltypeDirs);
+
+ /*
+ Check if the path obtained by the filelocations is the same as the DomItem path. Both
+ need to be equal to find DomItem's from their location in the source code.
+ */
+ auto compareFileLocationsPathWithCanonicalPath = [](const DomItem &item) {
+ Path canonical = item.canonicalPath();
+ QVERIFY(canonical.length() > 0);
+ if (canonical.length() > 0)
+ QCOMPARE(canonical.toString(),
+ FileLocations::treeOf(item)->canonicalPathForTesting());
+ };
+
+ /*
+ for all places, where scriptelements are attached to Qml elements in the Dom, check if:
+ a) scriptelement is accessible from the DomItem (is it correclty attached?)
+ b) scriptelement has the correct path (is its pathFromOwner the path where it was
+ attached?)
+
+ For bindings to objects, arrays and scripts, check that the bindingIdentifiers are correctly
+ attached in the Dom.
+ */
+
+ {
+ DomItem binding = rootQmlObject.field(Fields::bindings).key("binding");
+ QCOMPARE(binding.indexes(), 1);
+
+ QCOMPARE(binding.index(0).field(Fields::value).internalKind(),
+ DomType::ScriptExpression);
+ QCOMPARE(binding.index(0)
+ .field(Fields::value)
+ .field(Fields::scriptElement)
+ .internalKind(),
+ DomType::ScriptLiteral);
+ // Fields::value is in the path of the owner, and therefore should not be in
+ // pathFromOwner!
+ DomItem scriptElement =
+ binding.index(0).field(Fields::value).field(Fields::scriptElement);
+ QCOMPARE(scriptElement.pathFromOwner(), Path().field(Fields::scriptElement));
+ compareFileLocationsPathWithCanonicalPath(scriptElement);
+ }
+
+ {
+ DomItem bindingInPropertyDefinition =
+ rootQmlObject.field(Fields::bindings).key("bindingInPropertyDefinition");
+ QCOMPARE(bindingInPropertyDefinition.indexes(), 1);
+
+ QCOMPARE(bindingInPropertyDefinition.index(0).field(Fields::value).internalKind(),
+ DomType::ScriptExpression);
+ QCOMPARE(bindingInPropertyDefinition.index(0)
+ .field(Fields::value)
+ .field(Fields::scriptElement)
+ .internalKind(),
+ DomType::ScriptLiteral);
+ // Fields::value is in the path of the owner, and therefore should not be in
+ // pathFromOwner!
+ DomItem scriptElement = bindingInPropertyDefinition.index(0)
+ .field(Fields::value)
+ .field(Fields::scriptElement);
+ QCOMPARE(scriptElement.pathFromOwner(), Path().field(Fields::scriptElement));
+ compareFileLocationsPathWithCanonicalPath(scriptElement);
+ }
+ // check the parameters + returnType of the method
+ {
+ DomItem return42 = rootQmlObject.field(Fields::methods).key("return42");
+ QCOMPARE(return42.indexes(), 1);
+
+ DomItem typeAnnotation =
+ return42.index(0).field(Fields::returnType).field(Fields::scriptElement);
+ QCOMPARE(typeAnnotation.internalKind(), DomType::ScriptType);
+ compareFileLocationsPathWithCanonicalPath(typeAnnotation);
+
+ DomItem parameters = return42.index(0).field(Fields::parameters);
+ QCOMPARE(parameters.indexes(), 3);
+ for (int i = 0; i < 3; ++i) {
+ QCOMPARE(parameters.index(i).field(Fields::defaultValue).internalKind(),
+ DomType::ScriptExpression);
+ DomItem scriptElement =
+ parameters.index(i).field(Fields::value).field(Fields::scriptElement);
+ QCOMPARE(scriptElement.internalKind(), DomType::ScriptFormalParameter);
+ QCOMPARE(scriptElement.pathFromOwner(), Path().field(Fields::scriptElement));
+ compareFileLocationsPathWithCanonicalPath(scriptElement);
+ }
+ }
+ // check the body of the methods
+ QList<QString> methodNames = { "full", "return42" };
+ for (QString &methodName : methodNames) {
+ DomItem method = rootQmlObject.field(Fields::methods).key(methodName);
+ DomItem body = method.index(0).field(Fields::body);
+ QCOMPARE(body.internalKind(), DomType::ScriptExpression);
+ DomItem scriptElement = body.field(Fields::scriptElement);
+ QCOMPARE(scriptElement.internalKind(), DomType::ScriptBlockStatement);
+ QCOMPARE(scriptElement.pathFromOwner(), Path().field(Fields::scriptElement));
+ compareFileLocationsPathWithCanonicalPath(scriptElement);
+ }
+
+ {
+ DomItem binding = rootQmlObject.field(Fields::bindings).key("arrayBinding");
+ QCOMPARE(binding.indexes(), 1);
+ QCOMPARE(binding.index(0).field(Fields::value).internalKind(),
+ DomType::ScriptExpression);
+ QCOMPARE(binding.index(0)
+ .field(Fields::value)
+ .field(Fields::scriptElement)
+ .internalKind(),
+ DomType::ScriptArray);
+ // Fields::value is in the path of the owner, and therefore should not be in
+ // pathFromOwner!
+ DomItem scriptElement =
+ binding.index(0).field(Fields::value).field(Fields::scriptElement);
+ QCOMPARE(scriptElement.pathFromOwner(), Path().field(Fields::scriptElement));
+ compareFileLocationsPathWithCanonicalPath(scriptElement);
+ // also check that the left hand side of the binding is correctly attached to the Dom:
+ scriptElement = binding.index(0).field(Fields::bindingIdentifiers);
+ QCOMPARE(scriptElement.pathFromOwner(),
+ Path::fromString(u".components[\"\"][0].objects[0].bindings[\"arrayBinding\"]["
+ u"0].bindingIdentifiers"));
+ compareFileLocationsPathWithCanonicalPath(scriptElement);
+ }
+ {
+ DomItem binding = rootQmlObject.field(Fields::bindings).key("objectBinding");
+ QCOMPARE(binding.indexes(), 1);
+ QCOMPARE(binding.index(0).field(Fields::value).internalKind(), DomType::QmlObject);
+ // check that the left hand side of the binding is correctly attached to the Dom:
+ DomItem scriptElement = binding.index(0).field(Fields::bindingIdentifiers);
+ QCOMPARE(scriptElement.pathFromOwner(),
+ Path::fromString(u".components[\"\"][0].objects[0].bindings[\"objectBinding\"]["
+ u"0].bindingIdentifiers"));
+ compareFileLocationsPathWithCanonicalPath(scriptElement);
+ }
+ }
+
+ void goToFile()
+ {
+ using namespace Qt::StringLiterals;
+ const QString filePathA = baseDir + u"/nullStatements.qml"_s;
+ const QString filePathB = baseDir + u"/propertyBindings.qml"_s;
+ const QString canonicalFilePathB = QFileInfo(filePathB).canonicalFilePath();
+ QVERIFY(!canonicalFilePathB.isEmpty());
+
+ DomCreationOptions options;
+ options.setFlag(DomCreationOption::WithScriptExpressions);
+ options.setFlag(DomCreationOption::WithSemanticAnalysis);
+ auto envPtr = DomEnvironment::create(
+ qmltypeDirs,
+ QQmlJS::Dom::DomEnvironment::Option::SingleThreaded
+ | QQmlJS::Dom::DomEnvironment::Option::NoDependencies,
+ options);
+
+ DomItem fileA;
+ DomItem fileB;
+
+ envPtr->loadFile(FileToLoad::fromFileSystem(envPtr, filePathA),
+ [&fileA](Path, const DomItem &, const DomItem &newIt) {
+ fileA = newIt.fileObject();
+ });
+
+ envPtr->loadFile(FileToLoad::fromFileSystem(envPtr, filePathB),
+ [&fileB](Path, const DomItem &, const DomItem &newIt) {
+ fileB = newIt.fileObject();
+ });
+ envPtr->loadPendingDependencies();
+
+ QCOMPARE(fileA.goToFile(canonicalFilePathB), fileB);
+ }
+
+ void goUp()
+ {
+ using namespace Qt::StringLiterals;
+ const QString filePath = baseDir + u"/nullStatements.qml"_s;
+ const QString canonicalFilePathB = QFileInfo(filePath).canonicalFilePath();
+ QVERIFY(!canonicalFilePathB.isEmpty());
+
+ DomCreationOptions options;
+ options.setFlag(DomCreationOption::WithScriptExpressions);
+ options.setFlag(DomCreationOption::WithSemanticAnalysis);
+ auto envPtr = DomEnvironment::create(
+ qmltypeDirs,
+ QQmlJS::Dom::DomEnvironment::Option::SingleThreaded
+ | QQmlJS::Dom::DomEnvironment::Option::NoDependencies,
+ options);
+
+ DomItem fileA;
+ DomItem fileB;
+
+ envPtr->loadFile(FileToLoad::fromFileSystem(envPtr, filePath),
+ [&fileA](Path, const DomItem &, const DomItem &newIt) {
+ fileA = newIt.fileObject();
+ });
+
+ envPtr->loadPendingDependencies();
+
+ QCOMPARE(fileA.top().goUp(1), DomItem());
+ QCOMPARE(fileA.top().directParent(), DomItem());
+
+ DomItem component = fileA.field(Fields::components).key(QString()).index(0);
+
+ DomItem forStatement = component.field(Fields::objects)
+ .index(0)
+ .field(Fields::methods)
+ .key(u"testForNull"_s)
+ .index(0)
+ .field(Fields::body)
+ .field(Fields::scriptElement)
+ .field(Fields::statements)
+ .index(0)
+ .field(Fields::body);
+
+ DomItem forStatementBlock = forStatement.field(Fields::statements);
+
+ QCOMPARE(forStatementBlock.directParent(), forStatement);
+ QCOMPARE(forStatementBlock.goUp(1), forStatement);
+ QCOMPARE(forStatementBlock.goUp(11), component);
+
+ QCOMPARE(forStatement.component(GoTo::Strict), component);
+ }
+
+private:
+ static DomItem parse(const QString &path, const QStringList &qmltypeDirs)
+ {
+ DomCreationOptions options;
+ options.setFlag(DomCreationOption::WithScriptExpressions);
+ options.setFlag(DomCreationOption::WithSemanticAnalysis);
+
+ auto envPtr = DomEnvironment::create(
+ qmltypeDirs,
+ QQmlJS::Dom::DomEnvironment::Option::SingleThreaded
+ | QQmlJS::Dom::DomEnvironment::Option::NoDependencies,
+ options);
+
+ DomItem fileItem;
+
+ envPtr->loadFile(FileToLoad::fromFileSystem(envPtr, path),
+ [&fileItem](Path, const DomItem &, const DomItem &newIt) {
+ fileItem = newIt.fileObject();
+ });
+ envPtr->loadPendingDependencies();
+ return fileItem;
+ }
+
+ static DomItem rootQmlObjectFromFile(const QString &path, const QStringList &qmltypeDirs)
+ {
+ auto dom = parse(path, qmltypeDirs);
+ return dom.rootQmlObject(GoTo::MostLikely);
+ }
+
+ void fieldMemberExpressionHelper(const DomItem &actual, const QStringList &expected)
+ {
+ Q_ASSERT(!expected.isEmpty());
+ auto currentString = expected.rbegin();
+ auto endString = expected.rend();
+ DomItem current = actual;
+
+ for (; currentString != endString; ++currentString, current = current.field(Fields::left)) {
+ QCOMPARE(current.internalKind(), DomType::ScriptBinaryExpression);
+ QCOMPARE(current.field(Fields::operation).value().toInteger(),
+ ScriptElements::BinaryExpression::FieldMemberAccess);
+ QCOMPARE(current.field(Fields::right).internalKind(),
+ DomType::ScriptIdentifierExpression);
+ QCOMPARE(current.field(Fields::right).field(Fields::identifier).value().toString(),
+ *currentString);
+ }
+ }
+
+private slots:
+ void mapsKeyedByFileLocationRegion()
+ {
+ using namespace Qt::StringLiterals;
+ const QString filePath = baseDir + u"/fileLocationRegion.qml"_s;
+ const DomItem rootQmlObject = rootQmlObjectFromFile(filePath, qmltypeDirs);
+ QVERIFY(rootQmlObject);
+
+ // test if preComments map works correctly with DomItem interface
+ const DomItem binding = rootQmlObject.field(Fields::bindings).key(u"helloWorld"_s).index(0);
+ const DomItem bindingRegionComments =
+ binding.field(Fields::comments).field(Fields::regionComments);
+ const DomItem preComments =
+ bindingRegionComments.key(fileLocationRegionName(FileLocationRegion::IdentifierRegion))
+ .field(Fields::preComments);
+
+ QCOMPARE(preComments.indexes(), 1);
+ QString rawPreComment = preComments.index(0).field(Fields::rawComment).value().toString();
+ QCOMPARE(preComments.index(0)
+ .field(Fields::rawComment)
+ .value()
+ .toString()
+ // replace weird newlines by \n
+ .replace("\r\n", "\n")
+ .replace("\r", "\n"),
+ u" // before helloWorld binding\n "_s);
+
+ // test if postComments map works correctly with DomItem interface
+ const DomItem postComments =
+ bindingRegionComments
+ .key(fileLocationRegionName(FileLocationRegion::MainRegion))
+ .field(Fields::postComments);
+ QCOMPARE(postComments.indexes(), 1);
+ QCOMPARE(postComments.index(0)
+ .field(Fields::rawComment)
+ .value()
+ .toString()
+ // replace the windows newlines by \n
+ .replace("\r\n", "\n")
+ .replace("\r", "\n"),
+ u" // after helloWorld binding\n"_s);
+
+ const auto fileLocations = FileLocations::findAttachedInfo(binding);
+ const DomItem bindingFileLocation =
+ rootQmlObject.path(fileLocations.foundTreePath).field(Fields::infoItem);
+
+ // test if FileLocation Tree map works correctly with DomItem interface
+ QCOMPARE(bindingFileLocation.field(Fields::fullRegion).value(),
+ bindingFileLocation.field(Fields::regions)
+ .key(fileLocationRegionName(FileLocationRegion::MainRegion))
+ .value());
+
+ QCOMPARE(bindingFileLocation.field(Fields::fullRegion).value(),
+ sourceLocationToQCborValue(fileLocations.foundTree->info().fullRegion));
+
+ QCOMPARE(bindingFileLocation.field(Fields::regions)
+ .key(fileLocationRegionName(FileLocationRegion::MainRegion))
+ .value(),
+ sourceLocationToQCborValue(fileLocations.foundTree->info().regions[MainRegion]));
+
+ QCOMPARE(bindingFileLocation.field(Fields::regions)
+ .key(fileLocationRegionName(FileLocationRegion::ColonTokenRegion))
+ .value(),
+ sourceLocationToQCborValue(fileLocations.foundTree->info().regions[ColonTokenRegion]));
+ }
+
+ // add qml files here that should not crash the dom construction
+ void crashes_data()
+ {
+ QTest::addColumn<QString>("filePath");
+
+ QTest::addRow("inactiveVisitorMarkerCrash")
+ << baseDir + u"/inactiveVisitorMarkerCrash.qml"_s;
+
+ QTest::addRow("templateStrings")
+ << baseDir + u"/crashes/templateStrings.qml"_s;
+
+ QTest::addRow("lambda")
+ << baseDir + u"/crashes/lambda.qml"_s;
+ }
+ void crashes()
+ {
+ QFETCH(QString, filePath);
+
+ const DomItem rootQmlObject = rootQmlObjectFromFile(filePath, qmltypeDirs);
+ QVERIFY(rootQmlObject);
+ }
+
+ void continueStatement()
+ {
+ using namespace Qt::StringLiterals;
+ const QString testFile = baseDir + u"/continueStatement.qml"_s;
+ const DomItem rootQmlObject = rootQmlObjectFromFile(testFile, qmltypeDirs);
+ const DomItem block =
+ rootQmlObject.path(".methods[\"f\"][0].body.scriptElement.statements");
+
+ const DomItem firstContinue = block.index(0);
+ QCOMPARE(firstContinue.internalKind(), DomType::ScriptContinueStatement);
+ QCOMPARE(firstContinue.field(Fields::label).value().toString("UNEXISTING"),
+ u"helloWorld"_s);
+
+ const DomItem secondContinue = block.index(1);
+ QCOMPARE(secondContinue.internalKind(), DomType::ScriptContinueStatement);
+ QCOMPARE(secondContinue.field(Fields::label).internalKind(), DomType::Empty);
+ }
+
+ void breakStatement()
+ {
+ using namespace Qt::StringLiterals;
+ const QString testFile = baseDir + u"/breakStatement.qml"_s;
+ const DomItem rootQmlObject = rootQmlObjectFromFile(testFile, qmltypeDirs);
+ const DomItem block =
+ rootQmlObject.path(".methods[\"f\"][0].body.scriptElement.statements");
+
+ const DomItem firstContinue = block.index(0);
+ QCOMPARE(firstContinue.internalKind(), DomType::ScriptBreakStatement);
+ QCOMPARE(firstContinue.field(Fields::label).value().toString("UNEXISTING"),
+ u"helloWorld"_s);
+
+ const DomItem secondContinue = block.index(1);
+ QCOMPARE(secondContinue.internalKind(), DomType::ScriptBreakStatement);
+ QCOMPARE(secondContinue.field(Fields::label).internalKind(), DomType::Empty);
+ }
+
+ void emptyMethodBody()
+ {
+ using namespace Qt::StringLiterals;
+ const QString testFile = baseDir + u"/emptyMethodBody.qml"_s;
+ const DomItem rootQmlObject = rootQmlObjectFromFile(testFile, qmltypeDirs);
+ const DomItem block = rootQmlObject.path(".methods[\"f\"][0].body.scriptElement");
+
+ QCOMPARE(block.internalKind(), DomType::ScriptBlockStatement);
+ QCOMPARE(block.field(Fields::statements).indexes(), 0);
+ }
+
+ void commaExpression()
+ {
+ using namespace Qt::StringLiterals;
+ const QString testFile = baseDir + u"/commaExpression.qml"_s;
+ const DomItem rootQmlObject = rootQmlObjectFromFile(testFile, qmltypeDirs);
+ const DomItem commaExpression = rootQmlObject.path(".methods[\"f\"][0].body.scriptElement.statements[0]");
+
+ QCOMPARE(commaExpression.internalKind(), DomType::ScriptBinaryExpression);
+ QCOMPARE(commaExpression.field(Fields::right)
+ .field(Fields::identifier)
+ .value()
+ .toString(),
+ u"c"_s);
+ QCOMPARE(commaExpression.field(Fields::left)
+ .field(Fields::right)
+ .field(Fields::identifier)
+ .value()
+ .toString(),
+ u"b"_s);
+ QCOMPARE(commaExpression.field(Fields::left)
+ .field(Fields::left)
+ .field(Fields::identifier)
+ .value()
+ .toString(),
+ u"a"_s);
+ }
+
+ void conditionalExpression()
+ {
+ using namespace Qt::StringLiterals;
+ const QString testFile = baseDir + u"/conditionalExpression.qml"_s;
+ const DomItem rootQmlObject = rootQmlObjectFromFile(testFile, qmltypeDirs);
+ const DomItem commaExpression = rootQmlObject.path(".methods[\"f\"][0].body.scriptElement.statements[0]");
+
+ QCOMPARE(commaExpression.internalKind(), DomType::ScriptConditionalExpression);
+ QCOMPARE(commaExpression.field(Fields::condition)
+ .field(Fields::identifier)
+ .value()
+ .toString(),
+ u"a"_s);
+ QCOMPARE(commaExpression.field(Fields::consequence)
+ .field(Fields::identifier)
+ .value()
+ .toString(),
+ u"b"_s);
+ QCOMPARE(commaExpression.field(Fields::alternative)
+ .field(Fields::identifier)
+ .value()
+ .toString(),
+ u"c"_s);
+ }
+
+ void unaryExpression_data()
+ {
+ QTest::addColumn<QString>("fileName");
+ QTest::addColumn<DomType>("type");
+
+ const QString folder = baseDir + u"/unaryExpressions/"_s;
+
+ QTest::addRow("minus") << folder + u"unaryMinus.qml"_s << DomType::ScriptUnaryExpression;
+ QTest::addRow("plus") << folder + u"unaryPlus.qml"_s << DomType::ScriptUnaryExpression;
+ QTest::addRow("tilde") << folder + u"tilde.qml"_s << DomType::ScriptUnaryExpression;
+ QTest::addRow("not") << folder + u"not.qml"_s << DomType::ScriptUnaryExpression;
+ QTest::addRow("typeof") << folder + u"typeof.qml"_s << DomType::ScriptUnaryExpression;
+ QTest::addRow("delete") << folder + u"delete.qml"_s << DomType::ScriptUnaryExpression;
+ QTest::addRow("void") << folder + u"void.qml"_s << DomType::ScriptUnaryExpression;
+ QTest::addRow("increment") << folder + u"increment.qml"_s << DomType::ScriptUnaryExpression;
+ QTest::addRow("decrement") << folder + u"decrement.qml"_s << DomType::ScriptUnaryExpression;
+
+ // post stuff
+ QTest::addRow("postIncrement")
+ << folder + u"postIncrement.qml"_s << DomType::ScriptPostExpression;
+ QTest::addRow("postDecrement")
+ << folder + u"postDecrement.qml"_s << DomType::ScriptPostExpression;
+ }
+
+ void unaryExpression()
+ {
+ using namespace Qt::StringLiterals;
+ QFETCH(QString, fileName);
+ QFETCH(DomType, type);
+ const DomItem rootQmlObject = rootQmlObjectFromFile(fileName, qmltypeDirs);
+ const DomItem firstStatement =
+ rootQmlObject.path(".methods[\"f\"][0].body.scriptElement.statements[0]");
+
+ QCOMPARE(firstStatement.internalKind(), type);
+ QCOMPARE(firstStatement.field(Fields::expression)
+ .field(Fields::identifier)
+ .value()
+ .toString(),
+ u"a"_s);
+ }
+
+ void objectBindings()
+ {
+ using namespace Qt::StringLiterals;
+ const QString testFile = baseDir + u"/objectBindings.qml"_s;
+ const DomItem rootQmlObject = rootQmlObjectFromFile(testFile, qmltypeDirs);
+
+ const DomItem xBinding = rootQmlObject.path(".bindings[\"x\"][0].value");
+ QCOMPARE(xBinding.field(Fields::name).value().toString(), u"root.QQ.Drag");
+ QCOMPARE(xBinding.field(Fields::nameIdentifiers).internalKind(),
+ DomType::ScriptType);
+ QCOMPARE(xBinding.field(Fields::nameIdentifiers).field(Fields::typeName).internalKind(),
+ DomType::ScriptBinaryExpression);
+ QCOMPARE(xBinding.field(Fields::nameIdentifiers)
+ .field(Fields::typeName)
+ .field(Fields::operation)
+ .value()
+ .toInteger(-1),
+ ScriptElements::BinaryExpression::FieldMemberAccess);
+
+ QCOMPARE(xBinding.field(Fields::nameIdentifiers).field(Fields::typeName).field(Fields::left).internalKind(),
+ DomType::ScriptBinaryExpression);
+ QCOMPARE(xBinding.field(Fields::nameIdentifiers)
+ .field(Fields::typeName)
+ .field(Fields::left)
+ .field(Fields::right)
+ .value()
+ .toString(),
+ u"QQ");
+ QCOMPARE(xBinding.field(Fields::nameIdentifiers)
+ .field(Fields::typeName)
+ .field(Fields::left)
+ .field(Fields::left)
+ .value()
+ .toString(),
+ u"root");
+
+ const DomItem item = rootQmlObject.path(".children[0]");
+ QCOMPARE(item.field(Fields::nameIdentifiers).field(Fields::typeName).value().toString(),
+ u"Item");
+
+ const DomItem qqItem = rootQmlObject.path(".children[1]");
+ QCOMPARE(qqItem.field(Fields::nameIdentifiers)
+ .field(Fields::typeName)
+ .field(Fields::operation)
+ .value()
+ .toInteger(-1),
+ ScriptElements::BinaryExpression::FieldMemberAccess);
+ QCOMPARE(qqItem.field(Fields::nameIdentifiers)
+ .field(Fields::typeName)
+ .field(Fields::right)
+ .value()
+ .toString(),
+ u"Item");
+ QCOMPARE(qqItem.field(Fields::nameIdentifiers)
+ .field(Fields::typeName)
+ .field(Fields::left)
+ .value()
+ .toString(),
+ u"QQ");
+ }
+
+ void scriptExpression()
+ {
+ // verifying support of ECMA script modules by ScriptExpression
+ const ScriptExpression esmExport("export function a(){}",
+ ScriptExpression::ExpressionType::ESMCode);
+ QVERIFY(esmExport.localErrors().empty());
+ }
+
+ void semanticAnalysis()
+ {
+
+ DomItem baseItem;
+ DomItem derivedItem;
+ DomCreationOptions options;
+ options.setFlag(DomCreationOption::WithScriptExpressions);
+ options.setFlag(DomCreationOption::WithSemanticAnalysis);
+
+ auto envPtr =
+ DomEnvironment::create(qmltypeDirs, QQmlJS::Dom::DomEnvironment::Option{}, options);
+
+ envPtr->loadFile(
+ FileToLoad::fromFileSystem(envPtr, baseDir + u"/Base.qml"_s),
+ [&baseItem](Path, const DomItem &, const DomItem &newIt) {
+ baseItem = newIt.rootQmlObject(GoTo::MostLikely);
+ });
+
+ envPtr->loadFile(
+ FileToLoad::fromFileSystem(envPtr, baseDir + u"/Derived.qml"_s),
+ [&derivedItem](Path, const DomItem &, const DomItem &newIt) {
+ derivedItem = newIt.rootQmlObject(GoTo::MostLikely);
+ });
+ envPtr->loadPendingDependencies();
+
+ const auto baseScope = baseItem.semanticScope();
+ const auto derivedScope = derivedItem.semanticScope();
+
+ QCOMPARE_NE(baseScope, QQmlJSScope::ConstPtr{});
+ QCOMPARE(baseScope, derivedScope->baseType());
+ }
+
+ void propertyDefinitionScopes()
+ {
+ DomItem qmlObject;
+ DomCreationOptions options;
+ options.setFlag(DomCreationOption::WithScriptExpressions);
+ options.setFlag(DomCreationOption::WithSemanticAnalysis);
+
+ auto envPtr =
+ DomEnvironment::create(qmltypeDirs, QQmlJS::Dom::DomEnvironment::Option{}, options);
+
+ envPtr->loadFile(FileToLoad::fromFileSystem(envPtr, baseDir + u"/propertyBindings.qml"_s),
+ [&qmlObject](Path, const DomItem &, const DomItem &newIt) {
+ qmlObject = newIt.rootQmlObject(GoTo::MostLikely);
+ });
+ envPtr->loadPendingDependencies();
+
+ {
+ const auto a = qmlObject.field(Fields::propertyDefs).key(u"a").index(0);
+ const auto scopeA = a.semanticScope();
+ QCOMPARE_NE(scopeA, QQmlJSScope::ConstPtr{});
+ QCOMPARE(scopeA->scopeType(), QQmlSA::ScopeType::QMLScope);
+ }
+
+ {
+ const auto b = qmlObject.field(Fields::propertyDefs).key(u"b").index(0);
+ const auto scopeB = b.semanticScope();
+ QCOMPARE_NE(scopeB, QQmlJSScope::ConstPtr{});
+ QCOMPARE(scopeB->scopeType(), QQmlSA::ScopeType::QMLScope);
+ }
+ }
+
+ // simulate qmlls loading the same file twice like in QTBUG-123591
+ void loadFileTwice()
+ {
+ DomItem qmlObject;
+ DomItem qmlObject2;
+ DomCreationOptions options;
+ options.setFlag(DomCreationOption::WithScriptExpressions);
+ options.setFlag(DomCreationOption::WithSemanticAnalysis);
+ options.setFlag(DomCreationOption::WithRecovery);
+
+ std::shared_ptr<DomEnvironment> envPtr = DomEnvironment::create(
+ qmltypeDirs, QQmlJS::Dom::DomEnvironment::Option::SingleThreaded, options);
+
+ const QString fileName{ baseDir + u"/propertyBindings.qml"_s };
+ QFile file(fileName);
+ QVERIFY(file.open(QFile::ReadOnly));
+ const QString content = file.readAll();
+
+ envPtr->loadFile(FileToLoad::fromFileSystem(envPtr, baseDir + u"/propertyBindings.qml"_s),
+ [&qmlObject](Path, const DomItem &, const DomItem &newIt) {
+ qmlObject = newIt.rootQmlObject(GoTo::MostLikely);
+ });
+ envPtr->loadPendingDependencies();
+
+ // should not assert when loading the same file again
+ auto envPtrChild = envPtr->makeCopy(DomItem(envPtr));
+ envPtrChild->loadFile(
+ FileToLoad::fromMemory(envPtr, baseDir + u"/propertyBindings.qml"_s, content),
+ [&qmlObject2](Path, const DomItem &, const DomItem &newIt) {
+ qmlObject2 = newIt.rootQmlObject(GoTo::MostLikely);
+ });
+ envPtrChild->loadPendingDependencies();
+ }
+
+ void populateLazyFileBeforeCommitToBase()
+ {
+ DomItem qmlObject;
+ DomCreationOptions options;
+ options.setFlag(DomCreationOption::WithScriptExpressions);
+ options.setFlag(DomCreationOption::WithSemanticAnalysis);
+ options.setFlag(DomCreationOption::WithRecovery);
+
+ std::shared_ptr<DomEnvironment> envPtr = DomEnvironment::create(
+ qmltypeDirs, QQmlJS::Dom::DomEnvironment::Option::SingleThreaded, options);
+
+ const QString fileName{ QDir::cleanPath(baseDir + u"/propertyBindings.qml"_s) };
+
+ {
+ DomItem envChild = DomItem(envPtr).makeCopy(DomItem::CopyOption::EnvConnected).item();
+ auto envPtrChild = envChild.ownerAs<DomEnvironment>();
+ envPtrChild->loadFile(
+ FileToLoad::fromFileSystem(envPtrChild, fileName),
+ [&qmlObject](Path, const DomItem &, const DomItem &newIt) {
+ qmlObject = newIt.fileObject();
+ });
+ envPtrChild->loadPendingDependencies();
+
+ const DomItem childEnv = DomItem(envPtrChild->shared_from_this());
+ // populate the lazy file by accessing it via the DomItem interface
+ const DomItem mainComponent =
+ childEnv.field(Fields::qmlFileWithPath)
+ .key(fileName)
+ .field(Fields::currentItem)
+ .field(Fields::components)
+ .key(QString());
+ QVERIFY(mainComponent);
+
+ envPtrChild->commitToBase(DomItem(envPtrChild));
+ } // destroy the temporary environment that the file was loaded into
+
+ // also make sure that the main component also exists in the base environment after the
+ // commitToBase call.
+ const DomItem env = DomItem(envPtr->shared_from_this());
+ const DomItem mainComponent = env.field(Fields::qmlFileWithPath)
+ .key(fileName)
+ .field(Fields::currentItem)
+ .field(Fields::components)
+ .key(QString());
+ QVERIFY(mainComponent);
+ }
+
+ void populateLazyFileAfterCommitToBase()
+ {
+ DomItem qmlObject;
+ DomCreationOptions options;
+ options.setFlag(DomCreationOption::WithScriptExpressions);
+ options.setFlag(DomCreationOption::WithSemanticAnalysis);
+ options.setFlag(DomCreationOption::WithRecovery);
+
+ std::shared_ptr<DomEnvironment> envPtr = DomEnvironment::create(
+ qmltypeDirs, QQmlJS::Dom::DomEnvironment::Option::SingleThreaded, options);
+
+ const QString fileName{ QDir::cleanPath(baseDir + u"/propertyBindings.qml"_s) };
+
+ {
+ DomItem envChild = DomItem(envPtr).makeCopy(DomItem::CopyOption::EnvConnected).item();
+ auto envPtrChild = envChild.ownerAs<DomEnvironment>();
+ envPtrChild->loadFile(
+ FileToLoad::fromFileSystem(envPtrChild, fileName),
+ [&qmlObject](Path, const DomItem &, const DomItem &newIt) {
+ qmlObject = newIt.fileObject();
+ });
+ envPtrChild->loadPendingDependencies();
+ envPtrChild->commitToBase(DomItem(envPtrChild));
+ } // destroy the temporary environment that the file was loaded into
+
+ const DomItem env = DomItem(envPtr->shared_from_this());
+ // populate the lazy file by accessing it via the DomItem interface
+ const DomItem mainComponent = env.field(Fields::qmlFileWithPath)
+ .key(fileName)
+ .field(Fields::currentItem)
+ .field(Fields::components)
+ .key(QString());
+ QVERIFY(mainComponent);
+ }
+
+ void qtbug_124799()
+ {
+ // reproduces the completion crash in QTBUG-124799 that was actually not completion related:
+ // triggering the completion was triggering the population of a file, that led to a
+ // heap-use-after-free. The steps to reproduce the crash are following:
+ // 1. load a file in a temporary environment
+ // 2. grab an unpopulated qqmljsscope from the type resolver of the loaded file
+ // 3. destroy the temporary environment
+ // 4. update the loaded file with new content, to make sure the QQmlJSImporter (used to
+ // populate of qmlfiles) has no more strong references in the QmlFile.
+ // 5. populate the unpopulated qqmljsscope: its factory should have kept track that its
+ // environment is not the temporary one but the base one (because of the commitToBase()
+ // call) and use the correct QQmlJSImporter (if its the one from the temporary environment
+ // this will lead to the heap-use-after-free memory error you get when triggering
+ // completions before this fix)
+
+ DomItem qmlObject;
+ DomCreationOptions options;
+ options.setFlag(DomCreationOption::WithScriptExpressions);
+ options.setFlag(DomCreationOption::WithSemanticAnalysis);
+ options.setFlag(DomCreationOption::WithRecovery);
+
+ std::shared_ptr<DomEnvironment> envPtr = DomEnvironment::create(
+ qmltypeDirs, QQmlJS::Dom::DomEnvironment::Option::SingleThreaded, options);
+
+ const QString fileName{ QDir::cleanPath(baseDir + u"/propertyBindings.qml"_s) };
+
+ QQmlJSScope::ConstPtr populateAfterEnvironmentDestruction;
+
+ {
+ DomItem envChild = DomItem(envPtr).makeCopy(DomItem::CopyOption::EnvConnected).item();
+ auto envPtrChild = envChild.ownerAs<DomEnvironment>();
+ envPtrChild->loadFile(
+ FileToLoad::fromFileSystem(envPtrChild, fileName),
+ [&qmlObject](Path, const DomItem &, const DomItem &newIt) {
+ qmlObject = newIt.fileObject();
+ });
+ envPtrChild->loadPendingDependencies();
+
+ auto qmlFilePtr = qmlObject.ownerAs<QmlFile>();
+ auto resolver = qmlFilePtr->typeResolver();
+ // simulate completion by grabbing some type from the resolver
+ populateAfterEnvironmentDestruction = resolver->importedTypes()[u"Derived"_s].scope;
+ envPtrChild->commitToBase(DomItem(envPtrChild));
+ }
+
+ // update the file
+ {
+ DomItem envChild = DomItem(envPtr).makeCopy(DomItem::CopyOption::EnvConnected).item();
+ auto envPtrChild = envChild.ownerAs<DomEnvironment>();
+
+ // simulate user typing something
+ QFile file(fileName);
+ QVERIFY(file.open(QFile::ReadOnly));
+ const QString content = file.readAll();
+ const QString newContent = content + "\n // important comment here\n";
+ envPtrChild->loadFile(FileToLoad::fromMemory(envPtrChild, fileName, newContent),
+ [&qmlObject](Path, const DomItem &, const DomItem &newIt) {
+ qmlObject = newIt.fileObject();
+ });
+ envPtrChild->loadPendingDependencies();
+ envPtrChild->commitToBase(DomItem(envPtrChild));
+ }
+
+ // step 3: populate the lazy qqmljsscope, it should not crash
+ QCOMPARE(populateAfterEnvironmentDestruction->filePath(),
+ QDir::cleanPath(baseDir + u"/Derived.qml"_s));
+ }
+
+ void visitTreeFilter()
+ {
+ DomItem qmlObject;
+ DomCreationOptions options;
+ options.setFlag(DomCreationOption::WithScriptExpressions);
+ options.setFlag(DomCreationOption::WithSemanticAnalysis);
+
+ auto envPtr =
+ DomEnvironment::create(qmltypeDirs, QQmlJS::Dom::DomEnvironment::Option{}, options);
+
+ envPtr->loadFile(FileToLoad::fromFileSystem(envPtr, baseDir + u"/visitTreeFilter.qml"_s),
+ [&qmlObject](Path, const DomItem &, const DomItem &newIt) {
+ qmlObject = newIt.rootQmlObject(GoTo::MostLikely);
+ });
+ envPtr->loadPendingDependencies();
+
+ FieldFilter filter({}, { { QString(), QString::fromUtf16(Fields::propertyDefs) } });
+
+ // check if propertyDefs is visited without the filter
+ bool success = false;
+ qmlObject.visitTree(
+ Path(), emptyChildrenVisitor, VisitOption::Recurse | VisitOption::VisitSelf,
+ [&success](const Path &p, const DomItem &, bool) {
+ const QString pathString = p.toString();
+ if (p && p.checkHeadName(Fields::propertyDefs)) {
+ success = true;
+ }
+ return true;
+ },
+ emptyChildrenVisitor);
+ QVERIFY(success);
+
+ // check that propertyDefs is not visited with the filter
+ success = true;
+ qmlObject.visitTree(
+ Path(), emptyChildrenVisitor, VisitOption::Recurse | VisitOption::VisitSelf,
+ [&success](const Path &p, const DomItem &, bool) {
+ if (p && p.checkHeadName(Fields::propertyDefs)) {
+ qWarning() << "Filter did not filter propertyDefs at path" << p;
+ success = false;
+ }
+ return true;
+ },
+ emptyChildrenVisitor, filter);
+ QVERIFY(success);
+ }
+
+ void fileLocationRegions_data()
+ {
+ QTest::addColumn<QString>("filePath");
+ QTest::addColumn<FileLocationRegion>("region");
+ QTest::addColumn<QSet<QQmlJS::SourceLocation>>("expectedLocs");
+
+ QTest::newRow("import") << baseDir + u"/fileLocationRegions/imports.qml"_s << ImportTokenRegion <<
+ QSet {
+ QQmlJS::SourceLocation{112, 6, 4, 1},
+ QQmlJS::SourceLocation{127, 6, 5, 1},
+ };
+ QTest::newRow("importUri") << baseDir + u"/fileLocationRegions/imports.qml"_s << ImportUriRegion <<
+ QSet {
+ QQmlJS::SourceLocation{119, 7, 4, 8},
+ QQmlJS::SourceLocation{152, 16, 6, 8},
+ QQmlJS::SourceLocation{186, 9, 7, 8}
+ };
+ QTest::newRow("asToken") << baseDir + u"/fileLocationRegions/imports.qml"_s << AsTokenRegion <<
+ QSet {
+ QQmlJS::SourceLocation{169, 2, 6, 25}
+ };
+ QTest::newRow("version") << baseDir + u"/fileLocationRegions/imports.qml"_s << VersionRegion <<
+ QSet {
+ QQmlJS::SourceLocation{140, 4, 5, 14}
+ };
+ QTest::newRow("namespace") << baseDir + u"/fileLocationRegions/imports.qml"_s << IdNameRegion <<
+ QSet {
+ QQmlJS::SourceLocation{172, 6, 6, 28}
+ };
+
+ QTest::newRow("function") << baseDir + u"/fileLocationRegions/functions.qml"_s
+ << FunctionKeywordRegion
+ << QSet{ QQmlJS::SourceLocation{ 139, 9, 7, 5 },
+ QQmlJS::SourceLocation{ 195, 9, 10, 9 } };
+
+ QTest::newRow("signal") << baseDir + u"/fileLocationRegions/functions.qml"_s
+ << SignalKeywordRegion
+ << QSet{ QQmlJS::SourceLocation{ 234, 6, 13, 5 },
+ QQmlJS::SourceLocation{ 254, 6, 14, 5 } };
+ QTest::newRow("return-type-identifier")
+ << baseDir + u"/fileLocationRegions/functions.qml"_s << TypeIdentifierRegion
+ << QSet{ QQmlJS::SourceLocation{ 154, 3, 7, 20 },
+ QQmlJS::SourceLocation{ 216, 3, 10, 30 } };
+ QTest::newRow("function-parameter-type-identifier")
+ << baseDir + u"/fileLocationRegions/functions.qml"_s << TypeIdentifierRegion
+ << QSet{ QQmlJS::SourceLocation{ 209, 3, 10, 23 } };
+ QTest::newRow("signal-parameter-type-identifier")
+ << baseDir + u"/fileLocationRegions/functions.qml"_s << TypeIdentifierRegion
+ << QSet{ QQmlJS::SourceLocation{ 243, 3, 13, 14 },
+ QQmlJS::SourceLocation{ 267, 3, 14, 18 } };
+ QTest::newRow("signal-parameter-identifier")
+ << baseDir + u"/fileLocationRegions/functions.qml"_s << IdentifierRegion
+ << QSet{ QQmlJS::SourceLocation{ 247, 1, 13, 18 },
+ QQmlJS::SourceLocation{ 264, 1, 14, 15 } };
+
+ QTest::newRow("pragma-keyword")
+ << baseDir + u"/fileLocationRegions/pragmas.qml"_s << PragmaKeywordRegion
+ << QSet{ QQmlJS::SourceLocation{ 112, 6, 4, 1 },
+ QQmlJS::SourceLocation{ 129, 6, 5, 1 },
+ QQmlJS::SourceLocation{ 161, 6, 6, 1 },
+ QQmlJS::SourceLocation{ 204, 6, 7, 1 }};
+ QTest::newRow("pragmaId")
+ << baseDir + u"/fileLocationRegions/pragmas.qml"_s << IdentifierRegion
+ << QSet{ QQmlJS::SourceLocation{ 119, 9, 4, 8 },
+ QQmlJS::SourceLocation{ 136, 17, 5, 8 },
+ QQmlJS::SourceLocation{ 168, 25, 6, 8 },
+ QQmlJS::SourceLocation{ 211, 17, 7, 8 }};
+ QTest::newRow("pragmaValues")
+ << baseDir + u"/fileLocationRegions/pragmas.qml"_s << PragmaValuesRegion
+ << QSet{ QQmlJS::SourceLocation{ 155, 5, 5, 27 },
+ QQmlJS::SourceLocation{ 195, 8, 6, 35 },
+ QQmlJS::SourceLocation{ 230, 4, 7, 27 },
+ QQmlJS::SourceLocation{ 235, 11, 7, 32 }};
+
+ QTest::newRow("enum-keyword")
+ << baseDir + u"/fileLocationRegions/enums.qml"_s << EnumKeywordRegion
+ << QSet{ QQmlJS::SourceLocation{ 139, 4, 7, 5 }};
+ QTest::newRow("enum-id")
+ << baseDir + u"/fileLocationRegions/enums.qml"_s << IdentifierRegion
+ << QSet{ QQmlJS::SourceLocation{ 144, 3, 7, 10 }};
+ QTest::newRow("enum-member")
+ << baseDir + u"/fileLocationRegions/enums.qml"_s << IdentifierRegion
+ << QSet{ QQmlJS::SourceLocation{ 158, 3, 8, 9 },
+ QQmlJS::SourceLocation{ 175, 3, 9, 9 },
+ QQmlJS::SourceLocation{ 188, 3, 10, 9 }};
+ QTest::newRow("enum-value")
+ << baseDir + u"/fileLocationRegions/enums.qml"_s << EnumValueRegion
+ << QSet{ QQmlJS::SourceLocation{ 164, 1, 8, 15 },
+ QQmlJS::SourceLocation{ 194, 2, 10, 15 }};
+ }
+
+ void fileLocationRegions()
+ {
+ QFETCH(QString, filePath);
+ QFETCH(FileLocationRegion, region);
+ QFETCH(QSet<QQmlJS::SourceLocation>, expectedLocs);
+ auto envPtr = DomEnvironment::create(
+ QStringList(),
+ QQmlJS::Dom::DomEnvironment::Option::SingleThreaded
+ | QQmlJS::Dom::DomEnvironment::Option::NoDependencies);
+
+ QFile f(filePath);
+ QVERIFY(f.open(QIODevice::ReadOnly | QIODevice::Text));
+ QString code = f.readAll();
+ DomItem file;
+ envPtr->loadFile(FileToLoad::fromMemory(envPtr, filePath, code),
+ [&file](Path, const DomItem &, const DomItem &newIt) {
+ file = newIt.fileObject();
+ });
+ envPtr->loadPendingDependencies();
+
+ const auto tree = FileLocations::treeOf(file);
+ using AttachedInfo = AttachedInfoT<FileLocations>;
+ QSet<QQmlJS::SourceLocation> locs;
+ auto visitor = [&](const Path &currentPath, const AttachedInfo::Ptr &attachedInfo){
+ Q_UNUSED(currentPath);
+ const auto regions = attachedInfo->info().regions;
+ if (regions.contains(region)) {
+ locs << regions.value(region);
+ }
+ return true;
+ };
+ AttachedInfo::visitTree(tree, visitor, Path());
+ [&] {
+ QVERIFY(locs.contains(expectedLocs));
+ }();
+
+ if (QTest::currentTestFailed()) {
+ qDebug() << "Got:\n";
+ for (auto &x : locs) {
+ qDebug() << "Offset: " << x.offset
+ << ", Length:" << x.length
+ << ", Startline: " << x.startLine
+ << ", StartColumn: " << x.startColumn;
+ }
+ qDebug() << "But expected: \n";
+ for (auto &x : expectedLocs) {
+ qDebug() << "Offset: " << x.offset
+ << ", Length:" << x.length
+ << ", Startline: " << x.startLine
+ << ", StartColumn: " << x.startColumn;
+ }
+ }
+ }
+
+ void doNotCrashAtAstComments()
+ {
+ using namespace Qt::StringLiterals;
+ const QString testFile = baseDir + u"/astComments.qml"_s;
+ const DomItem fileObject = rootQmlObjectFromFile(testFile, qmltypeDirs).fileObject();
+ const DomItem astComments = fileObject.path(".astComments");
+
+ // Visiting astComment element shouldn't fail
+ QSet<QStringView> comments;
+ astComments.visitTree(
+ Path(),
+ [&comments](const Path &, const DomItem &item, bool) {
+ if (item.internalKind() == DomType::Comment) {
+ auto comment = item.as<Comment>();
+ comments << comment->rawComment();
+ }
+ return true;
+ }
+ );
+
+ QVERIFY(comments.contains(u"/*Ast Comment*/ "_s));
+ }
+
+ void commentLocations()
+ {
+ auto envPtr = DomEnvironment::create(
+ QStringList(),
+ QQmlJS::Dom::DomEnvironment::Option::SingleThreaded
+ | QQmlJS::Dom::DomEnvironment::Option::NoDependencies);
+
+ const auto filePath = baseDir + u"/fileLocationRegions/comments.qml"_s;
+ QFile f(filePath);
+ QVERIFY(f.open(QIODevice::ReadOnly | QIODevice::Text));
+ QString code = f.readAll();
+ DomItem file;
+ envPtr->loadFile(FileToLoad::fromMemory(envPtr, filePath, code),
+ [&file](Path, const DomItem &, const DomItem &newIt) {
+ file = newIt.fileObject();
+ });
+ envPtr->loadPendingDependencies();
+
+ const auto expctedCommentLocations = QSet {
+ QQmlJS::SourceLocation(0, 41, 1, 1),
+ QQmlJS::SourceLocation(42,68, 2, 1),
+ QQmlJS::SourceLocation(126,25, 6, 1),
+ QQmlJS::SourceLocation(152,14, 10, 1),
+ QQmlJS::SourceLocation(167,21, 11, 1)
+ };
+
+ QSet<SourceLocation> locs;
+ file.fileObject(GoTo::MostLikely).visitTree(Path(), [&locs](Path, const DomItem &item, bool){
+ if (item.internalKind() == DomType::Comment) {
+ const auto comment = item.as<Comment>();
+ if (comment) {
+ locs << comment->info().sourceLocation();
+ }
+ }
+ return true;
+ }, VisitOption::Default, emptyChildrenVisitor, emptyChildrenVisitor);
+
+
+ QCOMPARE(locs, expctedCommentLocations);
+ }
+
private:
QString baseDir;
QStringList qmltypeDirs;