2022-05-13 13:12:05 +00:00
|
|
|
// Copyright (C) 2021 The Qt Company Ltd.
|
2024-02-22 14:51:16 +00:00
|
|
|
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
|
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());
|
2024-03-23 19:41:57 +00:00
|
|
|
if (!f.open(QIODevice::ReadOnly))
|
|
|
|
qFatal("Could not open file %s", qPrintable(url));
|
|
|
|
QByteArray data = f.readAll();
|
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();
|
2024-04-05 09:04:22 +00:00
|
|
|
target->setOwnModuleName(u"HelloModule"_s);
|
2022-03-31 09:26:09 +00:00
|
|
|
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();
|
QQmlJSImportVisitor mark required aliases as required in their scope
When `QQmlJSImportVisitor` visits a program, it marks properties as
required in their scope when it encounters them or it encounters a
`required` statement.
For aliases, an additional step is performed at the end of a visit, when
the aliases are resolved.
After an alias is resolved to a target property, is "requiredness", is
set based on the required of the target property in its scope.
Due to the specific implementation, the alias will be set to required in
its scope if the target property is required in its scope and will be
set to be non-required otherwise, independently of its previous required
state.
When we have an alias that is itself required pointing to a property
that isn't required, for example:
```
Item {
id: self
property int foo
property alias alias: self.foo
required alias
}
```
The alias will always be marked as not required, as it always inherits
the required state of the target property, losing the required status
that was applied by the "required" statement.
To respect "required" statements in relation to aliases,
`QQmlJSImportVisitor::resolveAliasesAndIds` will now inherit the
"requiredness" of the target property only when the alias is not already
marked as required in its scope.
Fixes: QTBUG-123837
Change-Id: I7b0f6958287625f3f3a5d736d4c45a3e050d4502
Reviewed-by: Ulf Hermann <ulf.hermann@qt.io>
2024-03-29 10:52:43 +00:00
|
|
|
void requiredAlias();
|
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();
|
2024-01-24 17:48:38 +00:00
|
|
|
void hasOwnEnumerationKeys();
|
2024-04-05 09:04:22 +00:00
|
|
|
void ownModuleName();
|
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();
|
2024-04-02 09:23:56 +00:00
|
|
|
void methodAndSignalSourceLocation();
|
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
|
|
|
}
|
|
|
|
|
QQmlJSImportVisitor mark required aliases as required in their scope
When `QQmlJSImportVisitor` visits a program, it marks properties as
required in their scope when it encounters them or it encounters a
`required` statement.
For aliases, an additional step is performed at the end of a visit, when
the aliases are resolved.
After an alias is resolved to a target property, is "requiredness", is
set based on the required of the target property in its scope.
Due to the specific implementation, the alias will be set to required in
its scope if the target property is required in its scope and will be
set to be non-required otherwise, independently of its previous required
state.
When we have an alias that is itself required pointing to a property
that isn't required, for example:
```
Item {
id: self
property int foo
property alias alias: self.foo
required alias
}
```
The alias will always be marked as not required, as it always inherits
the required state of the target property, losing the required status
that was applied by the "required" statement.
To respect "required" statements in relation to aliases,
`QQmlJSImportVisitor::resolveAliasesAndIds` will now inherit the
"requiredness" of the target property only when the alias is not already
marked as required in its scope.
Fixes: QTBUG-123837
Change-Id: I7b0f6958287625f3f3a5d736d4c45a3e050d4502
Reviewed-by: Ulf Hermann <ulf.hermann@qt.io>
2024-03-29 10:52:43 +00:00
|
|
|
void tst_qqmljsscope::requiredAlias()
|
|
|
|
{
|
|
|
|
QQmlJSScope::ConstPtr root = run(u"requiredAlias.qml"_s);
|
|
|
|
QVERIFY(root);
|
|
|
|
|
|
|
|
// Check whether aliases marked as required are required
|
|
|
|
QVERIFY(root->isPropertyRequired("sameScopeAlias"));
|
|
|
|
QVERIFY(root->isPropertyRequired("innerScopeAlias"));
|
|
|
|
}
|
|
|
|
|
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());
|
2024-04-18 11:28:39 +00:00
|
|
|
QCOMPARE(unscoped.descriptiveName(), u"(invalid type)::foo with type baz (stored as bar)"_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);
|
2023-12-21 12:25:11 +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));
|
|
|
|
}
|
|
|
|
|
2024-01-24 17:48:38 +00:00
|
|
|
void tst_qqmljsscope::hasOwnEnumerationKeys()
|
|
|
|
{
|
|
|
|
QQmlJSScope::ConstPtr root = run(u"extensions.qml"_s);
|
|
|
|
QVERIFY(root);
|
|
|
|
QQmlJSScope::ConstPtr extendedDerived = root->childScopes().front();
|
|
|
|
QVERIFY(extendedDerived);
|
|
|
|
// test that enumeration keys from base cannot be found
|
|
|
|
QVERIFY(!extendedDerived->hasOwnEnumerationKey(u"ThisIsTheEnumFromExtended"_s));
|
|
|
|
QVERIFY(!extendedDerived->hasOwnEnumerationKey(u"ThisIsTheFlagFromExtended"_s));
|
|
|
|
|
|
|
|
QQmlJSScope::ConstPtr extended = extendedDerived->baseType();
|
|
|
|
QVERIFY(extended);
|
|
|
|
|
|
|
|
QVERIFY(extended->hasOwnEnumerationKey(u"ThisIsTheEnumFromExtended"_s));
|
|
|
|
QVERIFY(extended->hasOwnEnumerationKey(u"ThisIsTheFlagFromExtended"_s));
|
|
|
|
QVERIFY(!extended->hasOwnEnumerationKey(u"ThisIsTheEnumFromExtension"_s));
|
|
|
|
QVERIFY(!extended->hasOwnEnumerationKey(u"ThisIsTheFlagFromExtension"_s));
|
|
|
|
}
|
|
|
|
|
2024-04-05 09:04:22 +00:00
|
|
|
void tst_qqmljsscope::ownModuleName()
|
|
|
|
{
|
|
|
|
const QString moduleName = u"HelloModule"_s;
|
|
|
|
QQmlJSScope::ConstPtr root = run(u"ownModuleName.qml"_s);
|
|
|
|
QVERIFY(root);
|
|
|
|
QCOMPARE(root->moduleName(), moduleName);
|
|
|
|
QCOMPARE(root->ownModuleName(), moduleName);
|
|
|
|
|
|
|
|
QCOMPARE(root->childScopes().size(), 2);
|
|
|
|
QQmlJSScope::ConstPtr child = root->childScopes().front();
|
|
|
|
QVERIFY(child);
|
|
|
|
// only root and inline components have own module names, but the child should be able to query
|
|
|
|
// its component's module Name via moduleName()
|
|
|
|
QCOMPARE(child->ownModuleName(), QString());
|
|
|
|
QCOMPARE(child->moduleName(), moduleName);
|
|
|
|
|
|
|
|
QQmlJSScope::ConstPtr ic = root->childScopes()[1];
|
|
|
|
QVERIFY(ic);
|
|
|
|
QCOMPARE(ic->ownModuleName(), moduleName);
|
|
|
|
QCOMPARE(ic->moduleName(), moduleName);
|
|
|
|
|
|
|
|
QQmlJSScope::ConstPtr icChild = ic->childScopes().front();
|
|
|
|
QVERIFY(icChild);
|
|
|
|
QCOMPARE(icChild->ownModuleName(), QString());
|
|
|
|
QCOMPARE(icChild->moduleName(), moduleName);
|
|
|
|
}
|
|
|
|
|
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);
|
2023-08-30 08:35:00 +00:00
|
|
|
QCOMPARE(onCompletedBinding.scriptKind(), QQmlSA::ScriptBindingKind::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-08-30 08:35:00 +00:00
|
|
|
QQmlSA::ScriptBindingKind::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);
|
2023-08-30 08:35:00 +00:00
|
|
|
QCOMPARE(onXChangedBinding.scriptKind(), QQmlSA::ScriptBindingKind::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));
|
2023-12-21 12:25:11 +00:00
|
|
|
QVERIFY(document.javaScriptCompilationUnit->unitData());
|
|
|
|
getRuntimeInfoFromCompilationUnit(document.javaScriptCompilationUnit->unitData(),
|
2022-07-01 11:51:12 +00:00
|
|
|
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 { }
|
|
|
|
};
|
|
|
|
|
2023-10-13 13:42:18 +00:00
|
|
|
using PassManagerPtr = std::unique_ptr<
|
|
|
|
QQmlSA::PassManager, decltype(&QQmlSA::PassManagerPrivate::deletePassManager)>;
|
|
|
|
|
2023-05-23 07:21:59 +00:00
|
|
|
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, {}
|
|
|
|
};
|
2023-10-13 13:42:18 +00:00
|
|
|
|
|
|
|
PassManagerPtr manager(
|
|
|
|
QQmlSA::PassManagerPrivate::createPassManager(&v, &resolver),
|
|
|
|
&QQmlSA::PassManagerPrivate::deletePassManager);
|
|
|
|
|
|
|
|
TestPass pass{ manager.get() };
|
2023-05-23 07:21:59 +00:00
|
|
|
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, {}
|
|
|
|
};
|
2023-10-13 13:42:18 +00:00
|
|
|
|
|
|
|
PassManagerPtr manager(
|
|
|
|
QQmlSA::PassManagerPrivate::createPassManager(&v, &resolver),
|
|
|
|
&QQmlSA::PassManagerPrivate::deletePassManager);
|
|
|
|
|
|
|
|
TestPass pass{ manager.get() };
|
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
|
|
|
auto element = pass.resolveBuiltinType(typeName);
|
|
|
|
QCOMPARE(element.isNull(), !valid);
|
|
|
|
}
|
|
|
|
|
2024-04-02 09:23:56 +00:00
|
|
|
void tst_qqmljsscope::methodAndSignalSourceLocation()
|
|
|
|
{
|
|
|
|
QmlIR::Document document(false);
|
|
|
|
auto jsscope = run(u"methodAndSignalSourceLocation.qml"_s, false);
|
|
|
|
|
|
|
|
std::array<std::array<int, 9>, 2> offsetsByLineEnding = {
|
|
|
|
std::array{ 29, 51, 74, 102, 128, 160, 219, 235, 257 }, // 1 char line endings
|
|
|
|
std::array{ 32, 55, 79, 108, 135, 168, 231, 248, 271 } // 2 char line endinds
|
|
|
|
};
|
|
|
|
|
|
|
|
// Try to detect the size of line endings as they lead to different source locations
|
|
|
|
auto offset1 = jsscope->methods("f1")[0].sourceLocation().offset;
|
|
|
|
QVERIFY(offset1 == 29 || offset1 == 32);
|
|
|
|
bool oneCharEndings = offset1 == 29;
|
|
|
|
std::array<int, 9> &offsets = oneCharEndings ? offsetsByLineEnding[0] : offsetsByLineEnding[1];
|
|
|
|
|
|
|
|
using namespace QQmlJS;
|
|
|
|
QCOMPARE(jsscope->methods("f1")[0].sourceLocation(), SourceLocation(offsets[0], 17, 4, 5));
|
|
|
|
QCOMPARE(jsscope->methods("f2")[0].sourceLocation(), SourceLocation(offsets[1], 18, 5, 5));
|
|
|
|
QCOMPARE(jsscope->methods("f3")[0].sourceLocation(), SourceLocation(offsets[2], 23, 6, 5));
|
|
|
|
QCOMPARE(jsscope->methods("f4")[0].sourceLocation(), SourceLocation(offsets[3], 21, 7, 5));
|
|
|
|
QCOMPARE(jsscope->methods("f5")[0].sourceLocation(), SourceLocation(offsets[4], 27, 8, 5));
|
|
|
|
QCOMPARE(jsscope->methods("f6")[0].sourceLocation(), SourceLocation(offsets[5], oneCharEndings ? 53 : 55, 9, 5));
|
|
|
|
|
|
|
|
QCOMPARE(jsscope->methods("s1")[0].sourceLocation(), SourceLocation(offsets[6], 11, 13, 5));
|
|
|
|
QCOMPARE(jsscope->methods("s2")[0].sourceLocation(), SourceLocation(offsets[7], 17, 14, 5));
|
|
|
|
QCOMPARE(jsscope->methods("s3")[0].sourceLocation(), SourceLocation(offsets[8], 28, 15, 5));
|
|
|
|
}
|
|
|
|
|
2021-11-23 12:44:58 +00:00
|
|
|
QTEST_MAIN(tst_qqmljsscope)
|
|
|
|
#include "tst_qqmljsscope.moc"
|