2022-05-13 13:12:05 +00:00
|
|
|
// Copyright (C) 2021 The Qt Company Ltd.
|
|
|
|
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
|
2021-11-23 12:44:58 +00:00
|
|
|
|
|
|
|
#include <QtTest/QtTest>
|
|
|
|
#include <QtQuickTestUtils/private/qmlutils_p.h>
|
|
|
|
|
|
|
|
#include <QtCore/qfileinfo.h>
|
|
|
|
#include <QtCore/qdir.h>
|
|
|
|
#include <QtCore/qdiriterator.h>
|
|
|
|
#include <QtCore/qurl.h>
|
|
|
|
#include <QtCore/qlibraryinfo.h>
|
2022-03-30 10:50:46 +00:00
|
|
|
#include <QtCore/qscopedpointer.h>
|
2021-11-23 12:44:58 +00:00
|
|
|
#include <QtQml/qqmlengine.h>
|
|
|
|
#include <QtQml/qqmlcomponent.h>
|
2022-03-30 10:50:46 +00:00
|
|
|
#include <QtGui/qfont.h>
|
2021-11-23 12:44:58 +00:00
|
|
|
|
|
|
|
#include <QtQml/private/qqmlirbuilder_p.h>
|
|
|
|
#include <private/qqmljscompiler_p.h>
|
|
|
|
#include <private/qqmljsscope_p.h>
|
|
|
|
#include <private/qqmljsimporter_p.h>
|
|
|
|
#include <private/qqmljslogger_p.h>
|
|
|
|
#include <private/qqmljsimportvisitor_p.h>
|
|
|
|
#include <private/qqmljstyperesolver_p.h>
|
2022-03-18 13:16:09 +00:00
|
|
|
#include <QtQml/private/qqmljslexer_p.h>
|
|
|
|
#include <QtQml/private/qqmljsparser_p.h>
|
2022-07-01 11:51:12 +00:00
|
|
|
#include <private/qqmlcomponent_p.h>
|
2021-11-23 12:44:58 +00:00
|
|
|
|
2022-03-21 09:21:18 +00:00
|
|
|
using namespace Qt::StringLiterals;
|
|
|
|
|
2021-11-23 12:44:58 +00:00
|
|
|
class tst_qqmljsscope : public QQmlDataTest
|
|
|
|
{
|
|
|
|
Q_OBJECT
|
|
|
|
|
|
|
|
QString loadUrl(const QString &url)
|
|
|
|
{
|
|
|
|
const QFileInfo fi(url);
|
|
|
|
QFile f(fi.absoluteFilePath());
|
|
|
|
f.open(QIODevice::ReadOnly);
|
|
|
|
QByteArray data(fi.size(), Qt::Uninitialized);
|
2022-10-05 05:29:16 +00:00
|
|
|
f.read(data.data(), data.size());
|
2021-11-23 12:44:58 +00:00
|
|
|
return QString::fromUtf8(data);
|
|
|
|
}
|
|
|
|
|
2022-05-04 10:35:58 +00:00
|
|
|
QQmlJSScope::ConstPtr run(QString url, bool expectErrorsOrWarnings = false)
|
2022-03-31 15:29:24 +00:00
|
|
|
{
|
|
|
|
QmlIR::Document document(false);
|
2022-05-04 10:35:58 +00:00
|
|
|
return run(url, &document, expectErrorsOrWarnings);
|
2022-03-31 15:29:24 +00:00
|
|
|
}
|
|
|
|
|
2022-05-04 10:35:58 +00:00
|
|
|
QQmlJSScope::ConstPtr run(QString url, QmlIR::Document *document,
|
|
|
|
bool expectErrorsOrWarnings = false)
|
2021-11-23 12:44:58 +00:00
|
|
|
{
|
|
|
|
url = testFile(url);
|
|
|
|
const QString sourceCode = loadUrl(url);
|
|
|
|
if (sourceCode.isEmpty())
|
|
|
|
return QQmlJSScope::ConstPtr();
|
|
|
|
|
|
|
|
// NB: JS unit generated here is ignored, so use noop function
|
|
|
|
QQmlJSSaveFunction noop([](auto &&...) { return true; });
|
|
|
|
QQmlJSCompileError error;
|
|
|
|
[&]() {
|
2022-03-31 15:29:24 +00:00
|
|
|
QVERIFY2(qCompileQmlFile(*document, url, noop, nullptr, &error),
|
2021-11-23 12:44:58 +00:00
|
|
|
qPrintable(error.message));
|
|
|
|
}();
|
|
|
|
if (!error.message.isEmpty())
|
|
|
|
return QQmlJSScope::ConstPtr();
|
|
|
|
|
|
|
|
|
2021-11-18 13:26:29 +00:00
|
|
|
QQmlJSLogger logger;
|
|
|
|
logger.setFileName(url);
|
|
|
|
logger.setCode(sourceCode);
|
2022-05-04 10:35:58 +00:00
|
|
|
logger.setSilent(expectErrorsOrWarnings);
|
2022-03-31 09:26:09 +00:00
|
|
|
QQmlJSScope::Ptr target = QQmlJSScope::create();
|
|
|
|
QQmlJSImportVisitor visitor(target, &m_importer, &logger, dataDirectory());
|
2022-03-11 15:03:18 +00:00
|
|
|
QQmlJSTypeResolver typeResolver { &m_importer };
|
2022-03-31 15:29:24 +00:00
|
|
|
typeResolver.init(&visitor, document->program);
|
2022-05-04 10:35:58 +00:00
|
|
|
if (!expectErrorsOrWarnings) {
|
|
|
|
[&]() {
|
|
|
|
QVERIFY2(!logger.hasWarnings(), "Expected no warnings in this test");
|
|
|
|
QVERIFY2(!logger.hasErrors(), "Expected no errors in this test");
|
|
|
|
}();
|
|
|
|
}
|
|
|
|
if (QTest::currentTestFailed())
|
|
|
|
return QQmlJSScope::ConstPtr();
|
2021-11-23 12:44:58 +00:00
|
|
|
return visitor.result();
|
|
|
|
}
|
|
|
|
|
|
|
|
private Q_SLOTS:
|
|
|
|
void initTestCase() override;
|
|
|
|
|
|
|
|
void orderedBindings();
|
2022-01-27 14:02:00 +00:00
|
|
|
void signalCreationDifferences();
|
2022-02-03 13:39:58 +00:00
|
|
|
void allTypesAvailable();
|
2022-03-11 15:03:18 +00:00
|
|
|
void shadowing();
|
2022-03-18 13:16:09 +00:00
|
|
|
|
2022-03-15 15:43:38 +00:00
|
|
|
#ifdef LABS_QML_MODELS_PRESENT
|
|
|
|
void componentWrappedObjects();
|
2022-03-28 11:45:35 +00:00
|
|
|
void labsQmlModelsSanity();
|
2022-03-15 15:43:38 +00:00
|
|
|
#endif
|
|
|
|
void unknownCppBase();
|
2022-03-18 13:16:09 +00:00
|
|
|
void groupedProperties();
|
2022-04-06 09:52:25 +00:00
|
|
|
void descriptiveNameOfNull();
|
2022-03-30 10:50:46 +00:00
|
|
|
void groupedPropertiesConsistency();
|
2022-03-30 12:15:32 +00:00
|
|
|
void groupedPropertySyntax();
|
2022-03-30 15:16:11 +00:00
|
|
|
void attachedProperties();
|
2022-04-06 07:56:17 +00:00
|
|
|
void scriptIndices();
|
2022-05-03 15:08:37 +00:00
|
|
|
void extensions();
|
2022-05-19 13:41:24 +00:00
|
|
|
void emptyBlockBinding();
|
2022-05-25 13:18:56 +00:00
|
|
|
void resolvedNonUniqueScopes();
|
2022-07-01 11:51:12 +00:00
|
|
|
void compilationUnitsAreCompatible();
|
2023-05-23 07:21:59 +00:00
|
|
|
void attachedTypeResolution_data();
|
|
|
|
void attachedTypeResolution();
|
QQmlSA: Remove Element ctor taking a name, add resolveBuiltinType
The Element constructor taking a name would internally create a
QQmlJSScope with a matching internal name – without doing any validation
that such an internal name would be sensible. Additionally, you could
create an Element with it, but you coudln't do anything sensible with it
as Element only has a read-only API, and the constructed QQmlJSScope
only contains a name.
Lastly, we do not really want to expose the internalName as anything
more than a transparent id at most.
Checking users of the constructor, one quickly finds that the only usage
was inside the quick plugin, which needed it to get access to the
"function" type; it also relied on "inherits" only checking the
internalName.
As an alternative, we can provide a resolveBuiltinType which can be used
for further checking, and actually returns the correct type with all its
methods and attributens, instead of a mock type which happens to work in
equality checks.
Pick-to: 6.6
Change-Id: I212532053d1b5c898776a564f2011ba17b074079
Reviewed-by: Ulf Hermann <ulf.hermann@qt.io>
2023-07-03 14:41:51 +00:00
|
|
|
void builtinTypeResolution_data();
|
|
|
|
void builtinTypeResolution();
|
2021-11-23 12:44:58 +00:00
|
|
|
|
|
|
|
public:
|
2022-03-11 15:03:18 +00:00
|
|
|
tst_qqmljsscope()
|
|
|
|
: QQmlDataTest(QT_QMLTEST_DATADIR),
|
|
|
|
m_importer(
|
|
|
|
{
|
|
|
|
QLibraryInfo::path(QLibraryInfo::QmlImportsPath),
|
|
|
|
dataDirectory(),
|
2022-05-04 12:13:09 +00:00
|
|
|
// Note: to be able to import the QQmlJSScopeTests
|
|
|
|
// correctly, we need an additional import path. Use
|
|
|
|
// this application's binary directory as done by
|
|
|
|
// QQmlImportDatabase
|
|
|
|
QCoreApplication::applicationDirPath(),
|
2022-03-11 15:03:18 +00:00
|
|
|
},
|
|
|
|
nullptr)
|
|
|
|
{
|
|
|
|
}
|
|
|
|
|
|
|
|
private:
|
|
|
|
QQmlJSImporter m_importer;
|
2021-11-23 12:44:58 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
void tst_qqmljsscope::initTestCase()
|
|
|
|
{
|
|
|
|
QQmlDataTest::initTestCase();
|
|
|
|
|
|
|
|
QDirIterator it(dataDirectory(), QDirIterator::FollowSymlinks | QDirIterator::Subdirectories);
|
|
|
|
while (it.hasNext()) {
|
|
|
|
const QString url = it.next();
|
2022-03-21 09:21:18 +00:00
|
|
|
if (!url.endsWith(u".qml"_s)) // not interesting
|
2021-11-23 12:44:58 +00:00
|
|
|
continue;
|
|
|
|
const QFileInfo fi(url);
|
|
|
|
QVERIFY(fi.exists());
|
|
|
|
QFile f(fi.absoluteFilePath());
|
|
|
|
QVERIFY(f.open(QIODevice::ReadOnly));
|
|
|
|
}
|
2022-05-04 12:13:09 +00:00
|
|
|
|
|
|
|
// test that we can import the module shipped with this test: if we have no
|
|
|
|
// errors / warnings, it is a success
|
|
|
|
QVERIFY(run(u"importOwnModule.qml"_s));
|
2021-11-23 12:44:58 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void tst_qqmljsscope::orderedBindings()
|
|
|
|
{
|
2022-03-21 09:21:18 +00:00
|
|
|
QQmlJSScope::ConstPtr root = run(u"orderedBindings.qml"_s);
|
2021-11-23 12:44:58 +00:00
|
|
|
QVERIFY(root);
|
|
|
|
|
2022-03-21 09:21:18 +00:00
|
|
|
auto [pBindingsBegin, pBindingsEnd] = root->ownPropertyBindings(u"p"_s);
|
2022-05-19 13:41:24 +00:00
|
|
|
QCOMPARE(std::distance(pBindingsBegin, pBindingsEnd), 2);
|
2021-11-23 12:44:58 +00:00
|
|
|
|
|
|
|
// check that the bindings are properly ordered
|
2023-05-05 07:30:27 +00:00
|
|
|
QCOMPARE(pBindingsBegin->bindingType(), QQmlSA::BindingType::Object);
|
|
|
|
QCOMPARE(std::next(pBindingsBegin)->bindingType(), QQmlSA::BindingType::Interceptor);
|
2021-11-24 08:53:10 +00:00
|
|
|
|
2022-03-21 09:21:18 +00:00
|
|
|
auto [itemsBindingsBegin, itemsBindingsEnd] = root->ownPropertyBindings(u"items"_s);
|
2022-05-19 13:41:24 +00:00
|
|
|
QCOMPARE(std::distance(itemsBindingsBegin, itemsBindingsEnd), 2);
|
2021-11-24 08:53:10 +00:00
|
|
|
|
2023-05-05 07:30:27 +00:00
|
|
|
QCOMPARE(itemsBindingsBegin->bindingType(), QQmlSA::BindingType::Object);
|
|
|
|
QCOMPARE(std::next(itemsBindingsBegin)->bindingType(), QQmlSA::BindingType::Object);
|
2021-11-24 08:53:10 +00:00
|
|
|
|
2022-03-21 09:21:18 +00:00
|
|
|
QCOMPARE(itemsBindingsBegin->objectType()->baseTypeName(), u"Item"_s);
|
|
|
|
QCOMPARE(std::next(itemsBindingsBegin)->objectType()->baseTypeName(), u"Text"_s);
|
2021-11-23 12:44:58 +00:00
|
|
|
}
|
|
|
|
|
2022-01-27 14:02:00 +00:00
|
|
|
void tst_qqmljsscope::signalCreationDifferences()
|
|
|
|
{
|
2022-10-13 13:42:05 +00:00
|
|
|
QQmlJSScope::ConstPtr root = run(u"signalCreationDifferences.qml"_s, true);
|
2022-01-27 14:02:00 +00:00
|
|
|
QVERIFY(root);
|
|
|
|
|
2022-03-21 09:21:18 +00:00
|
|
|
QVERIFY(root->hasOwnProperty(u"myProperty"_s));
|
|
|
|
QVERIFY(root->hasOwnProperty(u"conflictingProperty"_s));
|
|
|
|
QCOMPARE(root->ownMethods(u"mySignal"_s).size(), 1);
|
2022-01-27 14:02:00 +00:00
|
|
|
|
2022-03-21 09:21:18 +00:00
|
|
|
const auto conflicting = root->ownMethods(u"conflictingPropertyChanged"_s);
|
2022-01-27 14:02:00 +00:00
|
|
|
QCOMPARE(conflicting.size(), 2);
|
2023-05-05 07:30:27 +00:00
|
|
|
QCOMPARE(conflicting[0].methodType(), QQmlJSMetaMethodType::Signal);
|
|
|
|
QCOMPARE(conflicting[1].methodType(), QQmlJSMetaMethodType::Signal);
|
2022-01-27 14:02:00 +00:00
|
|
|
|
|
|
|
const QQmlJSMetaMethod *explicitMethod = nullptr;
|
|
|
|
if (conflicting[0].isImplicitQmlPropertyChangeSignal())
|
|
|
|
explicitMethod = &conflicting[1];
|
|
|
|
else
|
|
|
|
explicitMethod = &conflicting[0];
|
2022-03-21 09:21:18 +00:00
|
|
|
QCOMPARE(explicitMethod->parameterNames(), QStringList({ u"a"_s, u"c"_s }));
|
2022-01-27 14:02:00 +00:00
|
|
|
}
|
|
|
|
|
2022-02-03 13:39:58 +00:00
|
|
|
void tst_qqmljsscope::allTypesAvailable()
|
|
|
|
{
|
|
|
|
const QStringList importPaths = {
|
|
|
|
QLibraryInfo::path(QLibraryInfo::QmlImportsPath),
|
|
|
|
dataDirectory(),
|
|
|
|
};
|
|
|
|
|
|
|
|
QQmlJSImporter importer { importPaths, /* resource file mapper */ nullptr };
|
2022-10-19 11:07:56 +00:00
|
|
|
const auto imported = importer.importModule(u"QtQml"_s);
|
2023-08-01 13:43:15 +00:00
|
|
|
QCOMPARE(imported.context(), QQmlJS::ContextualTypes::QML);
|
2022-10-19 11:07:56 +00:00
|
|
|
const auto types = imported.types();
|
2022-03-21 09:21:18 +00:00
|
|
|
QVERIFY(types.contains(u"$internal$.QObject"_s));
|
|
|
|
QVERIFY(types.contains(u"QtObject"_s));
|
|
|
|
QCOMPARE(types[u"$internal$.QObject"_s].scope, types[u"QtObject"_s].scope);
|
2022-02-03 13:39:58 +00:00
|
|
|
}
|
|
|
|
|
2022-03-11 15:03:18 +00:00
|
|
|
void tst_qqmljsscope::shadowing()
|
|
|
|
{
|
2022-03-21 09:21:18 +00:00
|
|
|
QQmlJSScope::ConstPtr root = run(u"shadowing.qml"_s);
|
2022-03-11 15:03:18 +00:00
|
|
|
QVERIFY(root);
|
|
|
|
|
|
|
|
QVERIFY(root->baseType());
|
|
|
|
|
|
|
|
// Check whether properties are properly shadowed
|
|
|
|
const auto properties = root->properties();
|
2022-03-21 09:21:18 +00:00
|
|
|
QVERIFY(properties.contains(u"property_not_shadowed"_s));
|
|
|
|
QVERIFY(properties.contains(u"property_shadowed"_s));
|
2022-03-11 15:03:18 +00:00
|
|
|
|
2022-03-21 09:21:18 +00:00
|
|
|
QCOMPARE(properties[u"property_not_shadowed"_s].typeName(), u"QString"_s);
|
|
|
|
QCOMPARE(properties[u"property_shadowed"_s].typeName(), u"int"_s);
|
2022-03-11 15:03:18 +00:00
|
|
|
|
|
|
|
// Check whether methods are properly shadowed
|
|
|
|
const auto methods = root->methods();
|
2022-03-21 09:21:18 +00:00
|
|
|
QCOMPARE(methods.count(u"method_not_shadowed"_s), 1);
|
|
|
|
QCOMPARE(methods.count(u"method_shadowed"_s), 1);
|
2022-03-11 15:03:18 +00:00
|
|
|
|
2022-03-21 09:21:18 +00:00
|
|
|
QCOMPARE(methods[u"method_not_shadowed"_s].parameterNames().size(), 1);
|
|
|
|
QCOMPARE(methods[u"method_shadowed"_s].parameterNames().size(), 0);
|
2022-03-11 15:03:18 +00:00
|
|
|
}
|
|
|
|
|
2022-03-15 15:43:38 +00:00
|
|
|
#ifdef LABS_QML_MODELS_PRESENT
|
|
|
|
void tst_qqmljsscope::componentWrappedObjects()
|
|
|
|
{
|
2022-03-21 09:21:18 +00:00
|
|
|
QQmlJSScope::ConstPtr root = run(u"componentWrappedObjects.qml"_s);
|
2022-03-15 15:43:38 +00:00
|
|
|
QVERIFY(root);
|
|
|
|
|
|
|
|
auto children = root->childScopes();
|
2022-08-25 16:13:14 +00:00
|
|
|
QCOMPARE(children.size(), 6);
|
2022-03-15 15:43:38 +00:00
|
|
|
|
|
|
|
const auto isGoodType = [](const QQmlJSScope::ConstPtr &type, const QString &propertyName,
|
|
|
|
bool isWrapped) {
|
|
|
|
return type->hasOwnProperty(propertyName)
|
|
|
|
&& type->isWrappedInImplicitComponent() == isWrapped;
|
|
|
|
};
|
|
|
|
|
2022-03-21 09:21:18 +00:00
|
|
|
QVERIFY(isGoodType(children[0], u"nonWrapped1"_s, false));
|
|
|
|
QVERIFY(isGoodType(children[1], u"nonWrapped2"_s, false));
|
|
|
|
QVERIFY(isGoodType(children[2], u"nonWrapped3"_s, false));
|
|
|
|
QVERIFY(isGoodType(children[3], u"wrapped"_s, true));
|
2022-08-25 16:13:14 +00:00
|
|
|
QCOMPARE(children[4]->childScopes().size(), 3);
|
|
|
|
QVERIFY(isGoodType(children[4]->childScopes()[0], u"wrapped"_s, true));
|
|
|
|
|
|
|
|
QCOMPARE(children[4]->childScopes()[1]->childScopes().size(), 1);
|
|
|
|
QVERIFY(isGoodType(children[4]->childScopes()[1]->childScopes()[0], u"wrapped2"_s, true));
|
|
|
|
QCOMPARE(children[4]->childScopes()[2]->childScopes().size(), 1);
|
|
|
|
QVERIFY(isGoodType(children[4]->childScopes()[2]->childScopes()[0], u"wrapped3"_s, false));
|
2022-03-15 15:43:38 +00:00
|
|
|
}
|
2022-03-28 11:45:35 +00:00
|
|
|
|
|
|
|
void tst_qqmljsscope::labsQmlModelsSanity()
|
|
|
|
{
|
2022-03-21 09:21:18 +00:00
|
|
|
QQmlJSScope::ConstPtr root = run(u"labsQmlModelsSanity.qml"_s);
|
2022-03-28 11:45:35 +00:00
|
|
|
QVERIFY(root);
|
|
|
|
auto children = root->childScopes();
|
|
|
|
QCOMPARE(children.size(), 1);
|
|
|
|
|
|
|
|
// DelegateChooser: it inherits QQmlAbstractDelegateComponent (from
|
|
|
|
// QmlModels) which inherits QQmlComponent. While
|
|
|
|
// QQmlAbstractDelegateComponent has no properties, QQmlComponent does. If
|
|
|
|
// the QmlModels dependency is lost, we don't "see" that DelegateChooser
|
|
|
|
// inherits QQmlComponent - and so has no properties from it, hence, we can
|
|
|
|
// test exactly that:
|
2022-03-21 09:21:18 +00:00
|
|
|
QVERIFY(children[0]->hasProperty(u"progress"_s));
|
|
|
|
QVERIFY(children[0]->hasProperty(u"status"_s));
|
|
|
|
QVERIFY(children[0]->hasProperty(u"url"_s));
|
2022-03-28 11:45:35 +00:00
|
|
|
}
|
2022-03-15 15:43:38 +00:00
|
|
|
#endif
|
|
|
|
|
|
|
|
void tst_qqmljsscope::unknownCppBase()
|
|
|
|
{
|
2022-05-04 10:35:58 +00:00
|
|
|
QQmlJSScope::ConstPtr root = run(u"unknownCppBaseAssigningToVar.qml"_s, true);
|
2022-03-15 15:43:38 +00:00
|
|
|
QVERIFY(root);
|
|
|
|
// we should not crash here, then it is a success
|
|
|
|
}
|
|
|
|
|
2022-03-18 13:16:09 +00:00
|
|
|
void tst_qqmljsscope::groupedProperties()
|
|
|
|
{
|
2022-03-21 09:21:18 +00:00
|
|
|
QQmlJSScope::ConstPtr root = run(u"groupProperties.qml"_s);
|
2022-03-18 13:16:09 +00:00
|
|
|
QVERIFY(root);
|
|
|
|
|
2022-03-21 09:21:18 +00:00
|
|
|
QVERIFY(root->hasProperty(u"anchors"_s));
|
|
|
|
const auto anchorBindings = root->propertyBindings(u"anchors"_s);
|
2022-03-18 13:16:09 +00:00
|
|
|
QVERIFY(!anchorBindings.isEmpty());
|
2022-03-29 15:19:36 +00:00
|
|
|
QCOMPARE(anchorBindings.size(), 2); // from type itself and from the base type
|
|
|
|
|
|
|
|
const auto getBindingsWithinGroup =
|
|
|
|
[&](QMultiHash<QString, QQmlJSMetaPropertyBinding> *bindings, qsizetype index) -> void {
|
|
|
|
const auto &binding = anchorBindings[index];
|
2023-05-05 07:30:27 +00:00
|
|
|
QCOMPARE(binding.bindingType(), QQmlSA::BindingType::GroupProperty);
|
2022-03-29 15:19:36 +00:00
|
|
|
auto anchorScope = binding.groupType();
|
|
|
|
QVERIFY(anchorScope);
|
|
|
|
*bindings = anchorScope->ownPropertyBindings();
|
|
|
|
};
|
|
|
|
|
|
|
|
const auto value = [](const QMultiHash<QString, QQmlJSMetaPropertyBinding> &bindings,
|
|
|
|
const QString &key) {
|
|
|
|
return bindings.value(key, QQmlJSMetaPropertyBinding(QQmlJS::SourceLocation {}));
|
|
|
|
};
|
|
|
|
|
|
|
|
QMultiHash<QString, QQmlJSMetaPropertyBinding> bindingsOfType;
|
|
|
|
getBindingsWithinGroup(&bindingsOfType, 0);
|
|
|
|
QCOMPARE(bindingsOfType.size(), 2);
|
2023-05-05 07:30:27 +00:00
|
|
|
QCOMPARE(value(bindingsOfType, u"left"_s).bindingType(), QQmlSA::BindingType::Script);
|
2022-03-21 09:21:18 +00:00
|
|
|
QCOMPARE(value(bindingsOfType, u"leftMargin"_s).bindingType(),
|
2023-05-05 07:30:27 +00:00
|
|
|
QQmlSA::BindingType::NumberLiteral);
|
2022-03-29 15:19:36 +00:00
|
|
|
|
|
|
|
QMultiHash<QString, QQmlJSMetaPropertyBinding> bindingsOfBaseType;
|
|
|
|
getBindingsWithinGroup(&bindingsOfBaseType, 1);
|
|
|
|
QCOMPARE(bindingsOfBaseType.size(), 1);
|
2023-05-05 07:30:27 +00:00
|
|
|
QCOMPARE(value(bindingsOfBaseType, u"top"_s).bindingType(), QQmlSA::BindingType::Script);
|
2022-03-18 13:16:09 +00:00
|
|
|
}
|
|
|
|
|
2022-04-06 09:52:25 +00:00
|
|
|
void tst_qqmljsscope::descriptiveNameOfNull()
|
|
|
|
{
|
|
|
|
QQmlJSRegisterContent nullContent;
|
2022-03-21 09:21:18 +00:00
|
|
|
QCOMPARE(nullContent.descriptiveName(), u"(invalid type)"_s);
|
2022-04-06 09:52:25 +00:00
|
|
|
|
|
|
|
QQmlJSScope::Ptr stored = QQmlJSScope::create();
|
2022-03-21 09:21:18 +00:00
|
|
|
stored->setInternalName(u"bar"_s);
|
2022-04-06 09:52:25 +00:00
|
|
|
QQmlJSMetaProperty property;
|
2022-03-21 09:21:18 +00:00
|
|
|
property.setPropertyName(u"foo"_s);
|
|
|
|
property.setTypeName(u"baz"_s);
|
2022-04-06 09:52:25 +00:00
|
|
|
QQmlJSRegisterContent unscoped = QQmlJSRegisterContent::create(
|
2023-08-14 10:37:42 +00:00
|
|
|
stored, property, QQmlJSRegisterContent::InvalidLookupIndex,
|
|
|
|
QQmlJSRegisterContent::InvalidLookupIndex, QQmlJSRegisterContent::ScopeProperty,
|
|
|
|
QQmlJSScope::ConstPtr());
|
2022-03-21 09:21:18 +00:00
|
|
|
QCOMPARE(unscoped.descriptiveName(), u"bar of (invalid type)::foo with type baz"_s);
|
2022-04-06 09:52:25 +00:00
|
|
|
}
|
|
|
|
|
2022-03-30 10:50:46 +00:00
|
|
|
void tst_qqmljsscope::groupedPropertiesConsistency()
|
|
|
|
{
|
|
|
|
{
|
|
|
|
QQmlEngine engine;
|
|
|
|
QQmlComponent component(&engine);
|
2022-03-21 09:21:18 +00:00
|
|
|
component.loadUrl(testFileUrl(u"groupPropertiesConsistency.qml"_s));
|
2022-03-30 10:50:46 +00:00
|
|
|
QVERIFY2(component.isReady(), qPrintable(component.errorString()));
|
|
|
|
QScopedPointer<QObject> root(component.create());
|
|
|
|
QVERIFY2(root, qPrintable(component.errorString()));
|
|
|
|
QFont font = qvariant_cast<QFont>(root->property("font"));
|
|
|
|
QCOMPARE(font.pixelSize(), 22);
|
|
|
|
}
|
|
|
|
|
|
|
|
{
|
2022-03-21 09:21:18 +00:00
|
|
|
QQmlJSScope::ConstPtr root = run(u"groupPropertiesConsistency.qml"_s);
|
2022-03-30 10:50:46 +00:00
|
|
|
QVERIFY(root);
|
|
|
|
|
2022-03-21 09:21:18 +00:00
|
|
|
const auto fontBindings = root->propertyBindings(u"font"_s);
|
2022-03-30 10:50:46 +00:00
|
|
|
QCOMPARE(fontBindings.size(), 2);
|
|
|
|
|
|
|
|
// The binding order in QQmlJSScope case is "reversed": first come
|
|
|
|
// bindings on the leaf type, followed by the bindings on the base type
|
2023-05-05 07:30:27 +00:00
|
|
|
QCOMPARE(fontBindings[0].bindingType(), QQmlSA::BindingType::GroupProperty);
|
|
|
|
QCOMPARE(fontBindings[1].bindingType(), QQmlSA::BindingType::Script);
|
2022-03-30 10:50:46 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-03-30 12:15:32 +00:00
|
|
|
void tst_qqmljsscope::groupedPropertySyntax()
|
|
|
|
{
|
2022-03-21 09:21:18 +00:00
|
|
|
QQmlJSScope::ConstPtr root = run(u"groupPropertySyntax.qml"_s);
|
2022-03-30 12:15:32 +00:00
|
|
|
QVERIFY(root);
|
|
|
|
|
2022-03-21 09:21:18 +00:00
|
|
|
const auto fontBindings = root->propertyBindings(u"font"_s);
|
2022-03-30 12:15:32 +00:00
|
|
|
QCOMPARE(fontBindings.size(), 1);
|
|
|
|
|
|
|
|
// The binding order in QQmlJSScope case is "reversed": first come
|
|
|
|
// bindings on the leaf type, followed by the bindings on the base type
|
2023-05-05 07:30:27 +00:00
|
|
|
QCOMPARE(fontBindings[0].bindingType(), QQmlSA::BindingType::GroupProperty);
|
2022-03-30 12:15:32 +00:00
|
|
|
auto fontScope = fontBindings[0].groupType();
|
|
|
|
QVERIFY(fontScope);
|
2022-05-25 14:24:24 +00:00
|
|
|
QCOMPARE(fontScope->accessSemantics(), QQmlJSScope::AccessSemantics::Value);
|
2022-03-30 12:15:32 +00:00
|
|
|
auto subbindings = fontScope->ownPropertyBindings();
|
|
|
|
QCOMPARE(subbindings.size(), 2);
|
|
|
|
|
|
|
|
const auto value = [](const QMultiHash<QString, QQmlJSMetaPropertyBinding> &bindings,
|
|
|
|
const QString &key) {
|
|
|
|
return bindings.value(key, QQmlJSMetaPropertyBinding(QQmlJS::SourceLocation {}));
|
|
|
|
};
|
|
|
|
|
2023-05-05 07:30:27 +00:00
|
|
|
QCOMPARE(value(subbindings, u"pixelSize"_s).bindingType(), QQmlSA::BindingType::NumberLiteral);
|
|
|
|
QCOMPARE(value(subbindings, u"bold"_s).bindingType(), QQmlSA::BindingType::BoolLiteral);
|
2022-03-30 12:15:32 +00:00
|
|
|
}
|
|
|
|
|
2022-03-30 15:16:11 +00:00
|
|
|
void tst_qqmljsscope::attachedProperties()
|
|
|
|
{
|
2022-03-21 09:21:18 +00:00
|
|
|
QQmlJSScope::ConstPtr root = run(u"attachedProperties.qml"_s);
|
2022-03-30 15:16:11 +00:00
|
|
|
QVERIFY(root);
|
|
|
|
|
2022-03-21 09:21:18 +00:00
|
|
|
const auto keysBindings = root->propertyBindings(u"Keys"_s);
|
2022-03-30 15:16:11 +00:00
|
|
|
QVERIFY(!keysBindings.isEmpty());
|
|
|
|
QCOMPARE(keysBindings.size(), 2); // from type itself and from the base type
|
|
|
|
|
|
|
|
const auto getBindingsWithinAttached =
|
|
|
|
[&](QMultiHash<QString, QQmlJSMetaPropertyBinding> *bindings, qsizetype index) -> void {
|
|
|
|
const auto &binding = keysBindings[index];
|
2023-05-05 07:30:27 +00:00
|
|
|
QCOMPARE(binding.bindingType(), QQmlSA::BindingType::AttachedProperty);
|
2022-03-30 15:16:11 +00:00
|
|
|
auto keysScope = binding.attachingType();
|
|
|
|
QVERIFY(keysScope);
|
2022-05-25 14:24:24 +00:00
|
|
|
QCOMPARE(keysScope->accessSemantics(), QQmlJSScope::AccessSemantics::Reference);
|
2022-03-30 15:16:11 +00:00
|
|
|
*bindings = keysScope->ownPropertyBindings();
|
|
|
|
};
|
|
|
|
|
|
|
|
const auto value = [](const QMultiHash<QString, QQmlJSMetaPropertyBinding> &bindings,
|
|
|
|
const QString &key) {
|
|
|
|
return bindings.value(key, QQmlJSMetaPropertyBinding(QQmlJS::SourceLocation {}));
|
|
|
|
};
|
|
|
|
|
|
|
|
QMultiHash<QString, QQmlJSMetaPropertyBinding> bindingsOfType;
|
|
|
|
getBindingsWithinAttached(&bindingsOfType, 0);
|
|
|
|
QCOMPARE(bindingsOfType.size(), 2);
|
2023-05-05 07:30:27 +00:00
|
|
|
QCOMPARE(value(bindingsOfType, u"enabled"_s).bindingType(), QQmlSA::BindingType::BoolLiteral);
|
|
|
|
QCOMPARE(value(bindingsOfType, u"forwardTo"_s).bindingType(), QQmlSA::BindingType::Script);
|
2022-03-30 15:16:11 +00:00
|
|
|
|
|
|
|
QMultiHash<QString, QQmlJSMetaPropertyBinding> bindingsOfBaseType;
|
|
|
|
getBindingsWithinAttached(&bindingsOfBaseType, 1);
|
|
|
|
QCOMPARE(bindingsOfBaseType.size(), 1);
|
2023-05-05 07:30:27 +00:00
|
|
|
QCOMPARE(value(bindingsOfBaseType, u"priority"_s).bindingType(), QQmlSA::BindingType::Script);
|
2022-03-30 15:16:11 +00:00
|
|
|
}
|
|
|
|
|
2022-03-31 15:29:24 +00:00
|
|
|
inline QString getScopeName(const QQmlJSScope::ConstPtr &scope)
|
|
|
|
{
|
|
|
|
Q_ASSERT(scope);
|
|
|
|
QQmlJSScope::ScopeType type = scope->scopeType();
|
2023-05-05 07:30:27 +00:00
|
|
|
if (type == QQmlSA::ScopeType::GroupedPropertyScope
|
|
|
|
|| type == QQmlSA::ScopeType::AttachedPropertyScope)
|
2022-03-31 15:29:24 +00:00
|
|
|
return scope->internalName();
|
|
|
|
return scope->baseTypeName();
|
|
|
|
}
|
|
|
|
|
2022-06-01 14:47:37 +00:00
|
|
|
struct FunctionOrExpressionIdentifier
|
|
|
|
{
|
|
|
|
QString name;
|
|
|
|
QQmlJS::SourceLocation loc = QQmlJS::SourceLocation {}; // source location of owning scope
|
|
|
|
int index = -1; // relative or absolute script index
|
|
|
|
FunctionOrExpressionIdentifier() = default;
|
|
|
|
FunctionOrExpressionIdentifier(const QString &name, const QQmlJS::SourceLocation &l, int i)
|
|
|
|
: name(name), loc(l), index(i)
|
|
|
|
{
|
|
|
|
}
|
|
|
|
FunctionOrExpressionIdentifier(const QString &name, const QV4::CompiledData::Location &l, int i)
|
|
|
|
: name(name), loc(QQmlJS::SourceLocation { 0, 0, l.line(), l.column() }), index(i)
|
|
|
|
{
|
|
|
|
}
|
|
|
|
|
|
|
|
friend bool operator<(const FunctionOrExpressionIdentifier &x,
|
|
|
|
const FunctionOrExpressionIdentifier &y)
|
|
|
|
{
|
|
|
|
// source location is taken from the owner scope so would be non-unique,
|
|
|
|
// while name must be unique within that scope. so: if source locations
|
|
|
|
// match, compare by name
|
|
|
|
if (x.loc == y.loc)
|
|
|
|
return x.name < y.name;
|
|
|
|
|
|
|
|
// otherwise, compare by start line first, if they match, then by column
|
|
|
|
if (x.loc.startLine == y.loc.startLine)
|
|
|
|
return x.loc.startColumn < y.loc.startColumn;
|
|
|
|
return x.loc.startLine < y.loc.startLine;
|
|
|
|
}
|
|
|
|
friend bool operator==(const FunctionOrExpressionIdentifier &x,
|
|
|
|
const FunctionOrExpressionIdentifier &y)
|
|
|
|
{
|
|
|
|
// equal-compare by name and index
|
|
|
|
return x.index == y.index && x.name == y.name;
|
|
|
|
}
|
|
|
|
friend bool operator!=(const FunctionOrExpressionIdentifier &x,
|
|
|
|
const FunctionOrExpressionIdentifier &y)
|
|
|
|
{
|
|
|
|
return !(x == y);
|
|
|
|
}
|
|
|
|
friend QDebug &operator<<(QDebug &stream, const FunctionOrExpressionIdentifier &x)
|
|
|
|
{
|
|
|
|
const QString dump = u"(%1, %2, loc %3:%4)"_s.arg(x.name, QString::number(x.index),
|
|
|
|
QString::number(x.loc.startLine),
|
|
|
|
QString::number(x.loc.startColumn));
|
|
|
|
stream << dump;
|
|
|
|
return stream.maybeSpace();
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2022-04-06 07:56:17 +00:00
|
|
|
void tst_qqmljsscope::scriptIndices()
|
2022-03-31 15:29:24 +00:00
|
|
|
{
|
|
|
|
{
|
|
|
|
QQmlEngine engine;
|
|
|
|
QQmlComponent component(&engine);
|
2022-03-21 09:21:18 +00:00
|
|
|
component.loadUrl(testFileUrl(u"functionAndBindingIndices.qml"_s));
|
2022-03-31 15:29:24 +00:00
|
|
|
QVERIFY2(component.isReady(), qPrintable(component.errorString()));
|
|
|
|
QScopedPointer<QObject> root(component.create());
|
|
|
|
QVERIFY2(root, qPrintable(component.errorString()));
|
|
|
|
}
|
|
|
|
|
|
|
|
QmlIR::Document document(false); // we need QmlIR information here
|
2022-03-21 09:21:18 +00:00
|
|
|
QQmlJSScope::ConstPtr root = run(u"functionAndBindingIndices.qml"_s, &document);
|
2022-03-31 15:29:24 +00:00
|
|
|
QVERIFY(root);
|
2022-04-06 07:56:17 +00:00
|
|
|
QVERIFY(document.javaScriptCompilationUnit.unitData());
|
2022-03-31 15:29:24 +00:00
|
|
|
|
2022-04-06 07:56:17 +00:00
|
|
|
// compare QQmlJSScope and QmlIR:
|
|
|
|
|
|
|
|
// {property, function}Name and relative (per-object) function table index
|
2022-06-01 14:47:37 +00:00
|
|
|
QList<FunctionOrExpressionIdentifier> orderedJSScopeExpressionsRelative;
|
|
|
|
QList<FunctionOrExpressionIdentifier> orderedQmlIrExpressionsRelative;
|
2022-04-06 07:56:17 +00:00
|
|
|
// {property, function}Name and absolute (per-document) function table index
|
2022-06-01 14:47:37 +00:00
|
|
|
QList<FunctionOrExpressionIdentifier> orderedJSScopeExpressionsAbsolute;
|
|
|
|
QList<FunctionOrExpressionIdentifier> orderedQmlIrExpressionsAbsolute;
|
2022-04-06 07:56:17 +00:00
|
|
|
|
|
|
|
const auto populateQQmlJSScopeArrays =
|
|
|
|
[&](const QQmlJSScope::ConstPtr &scope, const QString &name,
|
|
|
|
QQmlJSMetaMethod::RelativeFunctionIndex relativeIndex) {
|
2022-06-01 14:47:37 +00:00
|
|
|
orderedJSScopeExpressionsRelative.emplaceBack(name, scope->sourceLocation(),
|
2022-04-06 07:56:17 +00:00
|
|
|
static_cast<int>(relativeIndex));
|
|
|
|
auto absoluteIndex = scope->ownRuntimeFunctionIndex(relativeIndex);
|
2022-06-01 14:47:37 +00:00
|
|
|
orderedJSScopeExpressionsAbsolute.emplaceBack(name, scope->sourceLocation(),
|
2022-04-06 07:56:17 +00:00
|
|
|
static_cast<int>(absoluteIndex));
|
|
|
|
};
|
|
|
|
|
|
|
|
const auto populateQmlIRArrays = [&](const QmlIR::Object *irObject, const QString &name,
|
|
|
|
int relative) {
|
2022-06-01 14:47:37 +00:00
|
|
|
orderedQmlIrExpressionsRelative.emplaceBack(name, irObject->location, relative);
|
2022-04-06 07:56:17 +00:00
|
|
|
auto absolute = irObject->runtimeFunctionIndices.at(relative);
|
2022-06-01 14:47:37 +00:00
|
|
|
orderedQmlIrExpressionsAbsolute.emplaceBack(name, irObject->location, absolute);
|
2022-04-06 07:56:17 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
const auto suitableScope = [](const QQmlJSScope::ConstPtr &scope) {
|
|
|
|
const auto type = scope->scopeType();
|
2023-05-05 07:30:27 +00:00
|
|
|
return type == QQmlSA::ScopeType::QMLScope
|
|
|
|
|| type == QQmlSA::ScopeType::GroupedPropertyScope
|
|
|
|
|| type == QQmlSA::ScopeType::AttachedPropertyScope;
|
2022-04-06 07:56:17 +00:00
|
|
|
};
|
2022-03-31 15:29:24 +00:00
|
|
|
|
|
|
|
QList<QQmlJSScope::ConstPtr> queue;
|
|
|
|
queue.push_back(root);
|
|
|
|
while (!queue.isEmpty()) {
|
|
|
|
auto current = queue.front();
|
|
|
|
queue.pop_front();
|
|
|
|
|
2022-04-06 07:56:17 +00:00
|
|
|
if (suitableScope(current)) {
|
|
|
|
const auto methods = current->ownMethods();
|
|
|
|
for (const auto &method : methods) {
|
2023-05-05 07:30:27 +00:00
|
|
|
if (method.methodType() == QQmlJSMetaMethodType::Signal)
|
2022-04-06 07:56:17 +00:00
|
|
|
continue;
|
|
|
|
QString name = method.methodName();
|
|
|
|
auto relativeIndex = method.jsFunctionIndex();
|
|
|
|
QVERIFY2(static_cast<int>(relativeIndex) >= 0,
|
|
|
|
qPrintable(QStringLiteral("Method %1 from %2 has no index")
|
|
|
|
.arg(name, getScopeName(current))));
|
|
|
|
|
|
|
|
populateQQmlJSScopeArrays(current, name, relativeIndex);
|
|
|
|
}
|
|
|
|
|
|
|
|
const auto bindings = current->ownPropertyBindings();
|
|
|
|
for (const auto &binding : bindings) {
|
2023-05-05 07:30:27 +00:00
|
|
|
if (binding.bindingType() != QQmlSA::BindingType::Script)
|
2022-04-06 07:56:17 +00:00
|
|
|
continue;
|
|
|
|
QString name = binding.propertyName();
|
|
|
|
auto relativeIndex = binding.scriptIndex();
|
|
|
|
QVERIFY2(static_cast<int>(relativeIndex) >= 0,
|
|
|
|
qPrintable(QStringLiteral("Binding on property %1 from %2 has no index")
|
|
|
|
.arg(name, getScopeName(current))));
|
|
|
|
|
|
|
|
populateQQmlJSScopeArrays(current, name, relativeIndex);
|
|
|
|
}
|
2022-03-31 15:29:24 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
const auto children = current->childScopes();
|
|
|
|
for (const auto &c : children)
|
|
|
|
queue.push_back(c);
|
|
|
|
}
|
|
|
|
|
2022-10-06 09:30:50 +00:00
|
|
|
for (const QmlIR::Object *irObject : std::as_const(document.objects)) {
|
2022-03-31 15:29:24 +00:00
|
|
|
const QString objectName = document.stringAt(irObject->inheritedTypeNameIndex);
|
|
|
|
for (auto it = irObject->functionsBegin(); it != irObject->functionsEnd(); ++it) {
|
|
|
|
QString name = document.stringAt(it->nameIndex);
|
2022-04-06 07:56:17 +00:00
|
|
|
populateQmlIRArrays(irObject, name, it->index);
|
2022-03-31 15:29:24 +00:00
|
|
|
}
|
|
|
|
for (auto it = irObject->bindingsBegin(); it != irObject->bindingsEnd(); ++it) {
|
2022-05-04 13:26:30 +00:00
|
|
|
if (it->type() != QmlIR::Binding::Type_Script)
|
2022-03-31 15:29:24 +00:00
|
|
|
continue;
|
|
|
|
QString name = document.stringAt(it->propertyNameIndex);
|
|
|
|
int index = it->value.compiledScriptIndex;
|
2022-04-06 07:56:17 +00:00
|
|
|
populateQmlIRArrays(irObject, name, index);
|
2022-03-31 15:29:24 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-06-01 14:47:37 +00:00
|
|
|
std::sort(orderedJSScopeExpressionsRelative.begin(), orderedJSScopeExpressionsRelative.end());
|
|
|
|
std::sort(orderedQmlIrExpressionsRelative.begin(), orderedQmlIrExpressionsRelative.end());
|
2022-03-31 15:29:24 +00:00
|
|
|
|
2022-06-01 14:47:37 +00:00
|
|
|
std::sort(orderedJSScopeExpressionsAbsolute.begin(), orderedJSScopeExpressionsAbsolute.end());
|
|
|
|
std::sort(orderedQmlIrExpressionsAbsolute.begin(), orderedQmlIrExpressionsAbsolute.end());
|
2022-04-06 07:56:17 +00:00
|
|
|
QCOMPARE(orderedJSScopeExpressionsRelative, orderedQmlIrExpressionsRelative);
|
|
|
|
QCOMPARE(orderedJSScopeExpressionsAbsolute, orderedQmlIrExpressionsAbsolute);
|
2022-03-31 15:29:24 +00:00
|
|
|
}
|
|
|
|
|
2022-05-03 15:08:37 +00:00
|
|
|
void tst_qqmljsscope::extensions()
|
|
|
|
{
|
|
|
|
QQmlJSScope::ConstPtr root = run(u"extensions.qml"_s);
|
|
|
|
QVERIFY(root);
|
|
|
|
QVERIFY(root->isFullyResolved());
|
|
|
|
|
|
|
|
const auto childScopes = root->childScopes();
|
2022-05-06 12:48:22 +00:00
|
|
|
QCOMPARE(childScopes.size(), 5);
|
2022-05-03 15:08:37 +00:00
|
|
|
|
|
|
|
QCOMPARE(childScopes[0]->baseTypeName(), u"Extended"_s);
|
|
|
|
QCOMPARE(childScopes[1]->baseTypeName(), u"ExtendedIndirect"_s);
|
|
|
|
QCOMPARE(childScopes[2]->baseTypeName(), u"ExtendedTwice"_s);
|
2022-05-06 12:48:22 +00:00
|
|
|
QCOMPARE(childScopes[3]->baseTypeName(), u"NamespaceExtended"_s);
|
|
|
|
QCOMPARE(childScopes[4]->baseTypeName(), u"NonNamespaceExtended"_s);
|
2022-05-03 15:08:37 +00:00
|
|
|
QVERIFY(childScopes[0]->isFullyResolved());
|
|
|
|
QVERIFY(childScopes[1]->isFullyResolved());
|
|
|
|
QVERIFY(childScopes[2]->isFullyResolved());
|
2022-05-06 12:48:22 +00:00
|
|
|
QVERIFY(childScopes[3]->isFullyResolved());
|
|
|
|
QVERIFY(childScopes[4]->isFullyResolved());
|
2022-05-03 15:08:37 +00:00
|
|
|
|
|
|
|
QCOMPARE(childScopes[0]->property(u"count"_s).typeName(), u"int"_s);
|
|
|
|
QCOMPARE(childScopes[1]->property(u"count"_s).typeName(), u"double"_s);
|
|
|
|
QCOMPARE(childScopes[2]->property(u"count"_s).typeName(), u"int"_s);
|
|
|
|
QCOMPARE(childScopes[2]->property(u"str"_s).typeName(), u"QString"_s);
|
2022-05-06 12:48:22 +00:00
|
|
|
|
|
|
|
QVERIFY(!childScopes[3]->hasProperty(u"count"_s));
|
|
|
|
QVERIFY(!childScopes[3]->property(u"count"_s).isValid());
|
|
|
|
QVERIFY(!childScopes[3]->hasProperty(u"p"_s));
|
|
|
|
QVERIFY(!childScopes[3]->property(u"p"_s).isValid());
|
|
|
|
QVERIFY(!childScopes[3]->hasMethod(u"someMethod"_s));
|
|
|
|
QVERIFY(childScopes[3]->hasEnumeration(u"ExtensionEnum"_s));
|
|
|
|
QVERIFY(childScopes[3]->hasEnumerationKey(u"Value1"_s));
|
|
|
|
QVERIFY(childScopes[3]->enumeration(u"ExtensionEnum"_s).isValid());
|
|
|
|
QCOMPARE(childScopes[3]->defaultPropertyName(), u"objectName"_s);
|
|
|
|
QCOMPARE(childScopes[3]->parentPropertyName(), u"p"_s);
|
|
|
|
QVERIFY(!childScopes[3]->hasInterface(u"QQmlParserStatus"_s));
|
|
|
|
QCOMPARE(childScopes[3]->attachedTypeName(), QString());
|
|
|
|
QVERIFY(!childScopes[3]->attachedType());
|
|
|
|
|
|
|
|
QVERIFY(!childScopes[3]->extensionIsNamespace());
|
|
|
|
QVERIFY(childScopes[3]->baseType()->extensionIsNamespace());
|
|
|
|
|
|
|
|
QVERIFY(childScopes[4]->hasProperty(u"count"_s));
|
|
|
|
QVERIFY(childScopes[4]->property(u"count"_s).isValid());
|
|
|
|
QVERIFY(childScopes[4]->hasProperty(u"p"_s));
|
|
|
|
QVERIFY(childScopes[4]->property(u"p"_s).isValid());
|
|
|
|
QVERIFY(childScopes[4]->hasMethod(u"someMethod"_s));
|
|
|
|
QVERIFY(childScopes[4]->hasEnumeration(u"ExtensionEnum"_s));
|
|
|
|
QVERIFY(childScopes[4]->hasEnumerationKey(u"Value1"_s));
|
|
|
|
QVERIFY(childScopes[4]->enumeration(u"ExtensionEnum"_s).isValid());
|
|
|
|
QCOMPARE(childScopes[4]->defaultPropertyName(), u"objectName"_s);
|
|
|
|
QCOMPARE(childScopes[4]->parentPropertyName(), u"p"_s);
|
|
|
|
QVERIFY(!childScopes[4]->hasInterface(u"QQmlParserStatus"_s));
|
|
|
|
QCOMPARE(childScopes[4]->attachedTypeName(), QString());
|
|
|
|
QVERIFY(!childScopes[4]->attachedType());
|
|
|
|
|
|
|
|
auto [owner, ownerKind] = QQmlJSScope::ownerOfProperty(childScopes[4], u"count"_s);
|
|
|
|
QVERIFY(owner);
|
|
|
|
QCOMPARE(ownerKind, QQmlJSScope::ExtensionType);
|
|
|
|
QCOMPARE(owner, childScopes[4]->baseType()->extensionType().scope);
|
2022-05-03 15:08:37 +00:00
|
|
|
}
|
|
|
|
|
2022-05-19 13:41:24 +00:00
|
|
|
void tst_qqmljsscope::emptyBlockBinding()
|
|
|
|
{
|
|
|
|
QQmlJSScope::ConstPtr root = run(u"emptyBlockBinding.qml"_s);
|
|
|
|
QVERIFY(root);
|
|
|
|
QVERIFY(root->hasOwnPropertyBindings(u"x"_s));
|
|
|
|
QVERIFY(root->hasOwnPropertyBindings(u"y"_s));
|
|
|
|
}
|
|
|
|
|
2022-05-25 13:18:56 +00:00
|
|
|
void tst_qqmljsscope::resolvedNonUniqueScopes()
|
|
|
|
{
|
|
|
|
QQmlJSScope::ConstPtr root = run(u"resolvedNonUniqueScope.qml"_s);
|
|
|
|
QVERIFY(root);
|
|
|
|
|
|
|
|
const auto value = [](const QMultiHash<QString, QQmlJSMetaPropertyBinding> &bindings,
|
|
|
|
const QString &key) {
|
|
|
|
return bindings.value(key, QQmlJSMetaPropertyBinding(QQmlJS::SourceLocation {}));
|
|
|
|
};
|
|
|
|
|
|
|
|
{
|
|
|
|
auto topLevelBindings = root->propertyBindings(u"Component"_s);
|
|
|
|
QCOMPARE(topLevelBindings.size(), 1);
|
2023-05-05 07:30:27 +00:00
|
|
|
QCOMPARE(topLevelBindings[0].bindingType(), QQmlSA::BindingType::AttachedProperty);
|
2022-05-25 13:18:56 +00:00
|
|
|
auto componentScope = topLevelBindings[0].attachingType();
|
|
|
|
auto componentBindings = componentScope->ownPropertyBindings();
|
|
|
|
QCOMPARE(componentBindings.size(), 2);
|
|
|
|
auto onCompletedBinding = value(componentBindings, u"onCompleted"_s);
|
|
|
|
QVERIFY(onCompletedBinding.isValid());
|
2023-05-05 07:30:27 +00:00
|
|
|
QCOMPARE(onCompletedBinding.bindingType(), QQmlSA::BindingType::Script);
|
|
|
|
QCOMPARE(onCompletedBinding.scriptKind(), QQmlSA::ScriptBindingKind::Script_SignalHandler);
|
2022-05-25 13:18:56 +00:00
|
|
|
auto onDestructionBinding = value(componentBindings, u"onDestruction"_s);
|
|
|
|
QVERIFY(onDestructionBinding.isValid());
|
2023-05-05 07:30:27 +00:00
|
|
|
QCOMPARE(onDestructionBinding.bindingType(), QQmlSA::BindingType::Script);
|
2022-05-25 13:18:56 +00:00
|
|
|
QCOMPARE(onDestructionBinding.scriptKind(),
|
2023-05-05 07:30:27 +00:00
|
|
|
QQmlSA::ScriptBindingKind::Script_SignalHandler);
|
2022-05-25 13:18:56 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
{
|
|
|
|
auto topLevelBindings = root->propertyBindings(u"p"_s);
|
|
|
|
QCOMPARE(topLevelBindings.size(), 1);
|
2023-05-05 07:30:27 +00:00
|
|
|
QCOMPARE(topLevelBindings[0].bindingType(), QQmlSA::BindingType::GroupProperty);
|
2022-05-25 13:18:56 +00:00
|
|
|
auto pScope = topLevelBindings[0].groupType();
|
|
|
|
auto pBindings = pScope->ownPropertyBindings();
|
|
|
|
QCOMPARE(pBindings.size(), 1);
|
|
|
|
auto onXChangedBinding = value(pBindings, u"onXChanged"_s);
|
|
|
|
QVERIFY(onXChangedBinding.isValid());
|
2023-05-05 07:30:27 +00:00
|
|
|
QCOMPARE(onXChangedBinding.bindingType(), QQmlSA::BindingType::Script);
|
|
|
|
QCOMPARE(onXChangedBinding.scriptKind(), QQmlSA::ScriptBindingKind::Script_SignalHandler);
|
2022-05-25 13:18:56 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-07-01 11:51:12 +00:00
|
|
|
static void
|
|
|
|
getRuntimeInfoFromCompilationUnit(const QV4::CompiledData::Unit *unit,
|
|
|
|
QList<const QV4::CompiledData::Function *> &runtimeFunctions)
|
|
|
|
{
|
|
|
|
QVERIFY(unit);
|
|
|
|
for (uint i = 0; i < unit->functionTableSize; ++i) {
|
|
|
|
const QV4::CompiledData::Function *function = unit->functionAt(i);
|
|
|
|
QVERIFY(function);
|
|
|
|
runtimeFunctions << function;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Note: this test is here because we never explicitly test qCompileQmlFile()
|
|
|
|
void tst_qqmljsscope::compilationUnitsAreCompatible()
|
|
|
|
{
|
|
|
|
const QString url = u"compilationUnitsCompatibility.qml"_s;
|
|
|
|
QList<const QV4::CompiledData::Function *> componentFunctions;
|
|
|
|
QList<const QV4::CompiledData::Function *> cachegenFunctions;
|
|
|
|
|
|
|
|
QQmlEngine engine;
|
|
|
|
QQmlComponent component(&engine);
|
|
|
|
component.loadUrl(testFileUrl(url));
|
|
|
|
QVERIFY2(component.isReady(), qPrintable(component.errorString()));
|
|
|
|
QScopedPointer<QObject> root(component.create());
|
|
|
|
QVERIFY2(root, qPrintable(component.errorString()));
|
|
|
|
QQmlComponentPrivate *cPriv = QQmlComponentPrivate::get(&component);
|
|
|
|
QVERIFY(cPriv);
|
|
|
|
auto unit = cPriv->compilationUnit;
|
|
|
|
QVERIFY(unit);
|
|
|
|
QVERIFY(unit->unitData());
|
|
|
|
getRuntimeInfoFromCompilationUnit(unit->unitData(), componentFunctions);
|
|
|
|
|
|
|
|
if (QTest::currentTestFailed())
|
|
|
|
return;
|
|
|
|
|
|
|
|
QmlIR::Document document(false); // we need QmlIR information here
|
|
|
|
QVERIFY(run(url, &document));
|
|
|
|
QVERIFY(document.javaScriptCompilationUnit.unitData());
|
|
|
|
getRuntimeInfoFromCompilationUnit(document.javaScriptCompilationUnit.unitData(),
|
|
|
|
cachegenFunctions);
|
|
|
|
if (QTest::currentTestFailed())
|
|
|
|
return;
|
|
|
|
|
|
|
|
QCOMPARE(cachegenFunctions.size(), componentFunctions.size());
|
|
|
|
// name index should be fairly unique to distinguish different functions
|
|
|
|
// within a document. their order must be the same for both qmlcachegen and
|
|
|
|
// qqmltypecompiler (runtime)
|
|
|
|
for (qsizetype i = 0; i < cachegenFunctions.size(); ++i)
|
|
|
|
QCOMPARE(uint(cachegenFunctions[i]->nameIndex), uint(componentFunctions[i]->nameIndex));
|
|
|
|
}
|
|
|
|
|
2023-05-23 07:21:59 +00:00
|
|
|
void tst_qqmljsscope::attachedTypeResolution_data()
|
|
|
|
{
|
|
|
|
QTest::addColumn<bool>("creatable");
|
|
|
|
QTest::addColumn<QString>("moduleName");
|
|
|
|
QTest::addColumn<QString>("typeName");
|
|
|
|
QTest::addColumn<QString>("attachedTypeName");
|
|
|
|
QTest::addColumn<QString>("propertyOnSelf");
|
|
|
|
QTest::addColumn<QString>("propertyOnAttached");
|
|
|
|
|
|
|
|
QTest::addRow("ListView") << true
|
|
|
|
<< "QtQuick"
|
|
|
|
<< "ListView"
|
|
|
|
<< "QQuickListViewAttached"
|
|
|
|
<< "orientation"
|
|
|
|
<< "";
|
|
|
|
QTest::addRow("Keys") << false
|
|
|
|
<< "QtQuick"
|
|
|
|
<< "Keys"
|
|
|
|
<< "QQuickKeysAttached"
|
|
|
|
<< "priority"
|
|
|
|
<< "priority";
|
|
|
|
}
|
|
|
|
|
|
|
|
class TestPass : public QQmlSA::ElementPass
|
|
|
|
{
|
|
|
|
public:
|
|
|
|
TestPass(QQmlSA::PassManager *manager) : QQmlSA::ElementPass(manager) { }
|
|
|
|
bool shouldRun(const QQmlSA::Element &) override { return true; }
|
|
|
|
void run(const QQmlSA::Element &) override { }
|
|
|
|
};
|
|
|
|
|
|
|
|
void tst_qqmljsscope::attachedTypeResolution()
|
|
|
|
{
|
|
|
|
QFETCH(bool, creatable);
|
|
|
|
QFETCH(QString, moduleName);
|
|
|
|
QFETCH(QString, typeName);
|
|
|
|
QFETCH(QString, attachedTypeName);
|
|
|
|
QFETCH(QString, propertyOnSelf);
|
|
|
|
QFETCH(QString, propertyOnAttached);
|
|
|
|
|
|
|
|
std::unique_ptr<QQmlJSLogger> logger = std::make_unique<QQmlJSLogger>();
|
|
|
|
QFile qmlFile("data/attachedTypeResolution.qml");
|
|
|
|
if (!qmlFile.open(QIODevice::ReadOnly | QIODevice::Text))
|
|
|
|
QSKIP("Unable to open qml file");
|
|
|
|
|
|
|
|
logger->setCode(qmlFile.readAll());
|
|
|
|
logger->setFileName(QString(qmlFile.filesystemFileName().string().c_str()));
|
|
|
|
QQmlJSImporter importer{ { "data" }, nullptr, true };
|
|
|
|
QStringList defaultImportPaths =
|
|
|
|
QStringList{ QLibraryInfo::path(QLibraryInfo::QmlImportsPath) };
|
|
|
|
importer.setImportPaths(defaultImportPaths);
|
|
|
|
QQmlJSTypeResolver resolver(&importer);
|
|
|
|
const auto &implicitImportDirectory = QQmlJSImportVisitor::implicitImportDirectory(
|
|
|
|
logger->fileName(), importer.resourceFileMapper());
|
|
|
|
QQmlJSImportVisitor v{
|
|
|
|
QQmlJSScope::create(), &importer, logger.get(), implicitImportDirectory, {}
|
|
|
|
};
|
|
|
|
QQmlSA::PassManager manager{ &v, &resolver };
|
|
|
|
TestPass pass{ &manager };
|
|
|
|
const auto &resolved = pass.resolveType(moduleName, typeName);
|
|
|
|
|
|
|
|
QVERIFY(!resolved.isNull());
|
|
|
|
const auto &attachedType = pass.resolveAttached(moduleName, typeName);
|
|
|
|
QVERIFY(!attachedType.isNull());
|
2023-07-03 15:16:56 +00:00
|
|
|
QCOMPARE(attachedType.name(), attachedTypeName);
|
2023-05-23 07:21:59 +00:00
|
|
|
|
|
|
|
if (propertyOnAttached != "") {
|
|
|
|
QEXPECT_FAIL("Keys", "Keys and QQuickKeysAttached have the same properties", Continue);
|
|
|
|
QVERIFY(!resolved.hasProperty(propertyOnAttached));
|
|
|
|
QVERIFY(attachedType.hasProperty(propertyOnAttached));
|
|
|
|
}
|
|
|
|
if (propertyOnSelf != "") {
|
|
|
|
QEXPECT_FAIL("Keys", "Keys and QQuickKeysAttached have the same properties", Continue);
|
|
|
|
QVERIFY(!attachedType.hasProperty(propertyOnSelf));
|
|
|
|
}
|
|
|
|
|
|
|
|
if (creatable)
|
|
|
|
QVERIFY(resolved.hasProperty(propertyOnSelf));
|
|
|
|
}
|
|
|
|
|
QQmlSA: Remove Element ctor taking a name, add resolveBuiltinType
The Element constructor taking a name would internally create a
QQmlJSScope with a matching internal name – without doing any validation
that such an internal name would be sensible. Additionally, you could
create an Element with it, but you coudln't do anything sensible with it
as Element only has a read-only API, and the constructed QQmlJSScope
only contains a name.
Lastly, we do not really want to expose the internalName as anything
more than a transparent id at most.
Checking users of the constructor, one quickly finds that the only usage
was inside the quick plugin, which needed it to get access to the
"function" type; it also relied on "inherits" only checking the
internalName.
As an alternative, we can provide a resolveBuiltinType which can be used
for further checking, and actually returns the correct type with all its
methods and attributens, instead of a mock type which happens to work in
equality checks.
Pick-to: 6.6
Change-Id: I212532053d1b5c898776a564f2011ba17b074079
Reviewed-by: Ulf Hermann <ulf.hermann@qt.io>
2023-07-03 14:41:51 +00:00
|
|
|
|
|
|
|
|
|
|
|
void tst_qqmljsscope::builtinTypeResolution_data()
|
|
|
|
{
|
|
|
|
QTest::addColumn<bool>("valid");
|
|
|
|
QTest::addColumn<QString>("typeName");
|
|
|
|
|
|
|
|
QTest::addRow("global_QtObject") << true << "Qt";
|
|
|
|
QTest::addRow("function") << true << "function";
|
|
|
|
QTest::addRow("Array") << true << "Array";
|
|
|
|
QTest::addRow("invalid") << false << "foobar";
|
|
|
|
QTest::addRow("Number") << true << "Number";
|
|
|
|
QTest::addRow("bool") << true << "bool";
|
|
|
|
QTest::addRow("QString") << true << "QString";
|
|
|
|
}
|
|
|
|
|
|
|
|
void tst_qqmljsscope::builtinTypeResolution()
|
|
|
|
{
|
|
|
|
QFETCH(bool, valid);
|
|
|
|
QFETCH(QString, typeName);
|
|
|
|
|
|
|
|
QQmlJSImporter importer{ { "data" }, nullptr, true };
|
|
|
|
QStringList defaultImportPaths =
|
|
|
|
QStringList{ QLibraryInfo::path(QLibraryInfo::QmlImportsPath) };
|
|
|
|
importer.setImportPaths(defaultImportPaths);
|
|
|
|
QQmlJSTypeResolver resolver(&importer);
|
|
|
|
const auto &implicitImportDirectory = QQmlJSImportVisitor::implicitImportDirectory({}, nullptr);
|
|
|
|
QQmlJSLogger logger;
|
|
|
|
QQmlJSImportVisitor v{
|
|
|
|
QQmlJSScope::create(), &importer, &logger, implicitImportDirectory, {}
|
|
|
|
};
|
|
|
|
QQmlSA::PassManager manager{ &v, &resolver };
|
|
|
|
TestPass pass{ &manager };
|
|
|
|
auto element = pass.resolveBuiltinType(typeName);
|
|
|
|
QCOMPARE(element.isNull(), !valid);
|
|
|
|
}
|
|
|
|
|
2021-11-23 12:44:58 +00:00
|
|
|
QTEST_MAIN(tst_qqmljsscope)
|
|
|
|
#include "tst_qqmljsscope.moc"
|