qmltc: support inline components from different qml files
Make qmltc aware that inline components can be shared between files such that it does not complain about not finding them. Typical usage are `MyOtherFile.MyInlineComponent {}` and `ModuleName.MyOtherFile.MyInlineComponent {}` for an an inline component called MyInlineComponent defined in MyOtherFile.qml, maybe also in a module called ModuleName. Make QQmlJSScope::findType() aware that inline components exists (and potentially that they might also appear in namespaced types). They can also be "imported" or reexported from basetypes and/or deeply nested in some children scopes. Also make it public so that qqmljsimportvisitor can use it when processing property types. Added some tests testing both notations (with and without the qualifed module name). Also add a test to see if there is no confusion between the enums and the inline components (due to their very similar notations). Fixes: QTBUG-106592 Change-Id: I8f2d4790729902ffa664fd0eb1b7c3279af8ddca Reviewed-by: Qt CI Bot <qt_ci_bot@qt-project.org> Reviewed-by: Ulf Hermann <ulf.hermann@qt.io>
This commit is contained in:
parent
728270a0cb
commit
bfdf1bf797
|
@ -604,7 +604,8 @@ void QQmlJSImportVisitor::processPropertyTypes()
|
|||
|
||||
auto property = type.scope->ownProperty(type.name);
|
||||
|
||||
if (auto propertyType = QQmlJSScope::findType(property.typeName(), m_rootScopeImports).scope) {
|
||||
if (const auto propertyType =
|
||||
QQmlJSScope::findType(property.typeName(), m_rootScopeImports).scope) {
|
||||
property.setType(propertyType);
|
||||
type.scope->addOwnProperty(property);
|
||||
} else {
|
||||
|
|
|
@ -326,6 +326,43 @@ QQmlJSScope::findJSIdentifier(const QString &id) const
|
|||
return std::optional<JavaScriptIdentifier>{};
|
||||
}
|
||||
|
||||
static QQmlJSScope::ImportedScope<QQmlJSScope::ConstPtr>
|
||||
qFindInlineComponents(QStringView typeName, const QQmlJSScope::ContextualTypes &contextualTypes)
|
||||
{
|
||||
const int separatorIndex = typeName.lastIndexOf(u'.');
|
||||
// do not crash in typeName.sliced() when it starts or ends with an '.'.
|
||||
if (separatorIndex < 1 || separatorIndex >= typeName.size() - 1)
|
||||
return {};
|
||||
|
||||
const auto parentIt = contextualTypes.types.constFind(typeName.first(separatorIndex).toString());
|
||||
if (parentIt == contextualTypes.types.constEnd())
|
||||
return {};
|
||||
|
||||
auto inlineComponentParent = *parentIt;
|
||||
|
||||
// find the inline components using BFS, as inline components defined in childrens are also
|
||||
// accessible from other qml documents. Same for inline components defined in a base class of
|
||||
// the parent. Use BFS over DFS as the inline components are probably not deeply-nested.
|
||||
|
||||
QStringView inlineComponentName = typeName.sliced(separatorIndex + 1);
|
||||
QQueue<QQmlJSScope::ConstPtr> candidatesForInlineComponents;
|
||||
candidatesForInlineComponents.enqueue(inlineComponentParent.scope);
|
||||
while (candidatesForInlineComponents.size()) {
|
||||
QQmlJSScope::ConstPtr current = candidatesForInlineComponents.dequeue();
|
||||
if (!current) // if some type was not resolved, ignore it instead of crashing
|
||||
continue;
|
||||
if (current->isInlineComponent() && current->inlineComponentName() == inlineComponentName) {
|
||||
return { current, inlineComponentParent.revision };
|
||||
}
|
||||
// check alternatively the inline components at layer 1 in current and basetype, then at
|
||||
// layer 2, etc...
|
||||
candidatesForInlineComponents.append(current->childScopes());
|
||||
if (const auto base = current->baseType())
|
||||
candidatesForInlineComponents.enqueue(base);
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
QQmlJSScope::ImportedScope<QQmlJSScope::ConstPtr> QQmlJSScope::findType(
|
||||
const QString &name, const QQmlJSScope::ContextualTypes &contextualTypes,
|
||||
QSet<QString> *usedTypes)
|
||||
|
@ -364,11 +401,15 @@ QQmlJSScope::ImportedScope<QQmlJSScope::ConstPtr> QQmlJSScope::findType(
|
|||
break;
|
||||
}
|
||||
case ContextualTypes::QML: {
|
||||
// TODO look for inline components with qmlFileName.InlineComponentName syntax
|
||||
// look after inline components
|
||||
const auto inlineComponent = qFindInlineComponents(name, contextualTypes);
|
||||
if (inlineComponent.scope) {
|
||||
useType();
|
||||
return inlineComponent;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
|
|
|
@ -614,7 +614,7 @@ public:
|
|||
* If a type is found, then its name is inserted into usedTypes (when provided).
|
||||
* If contextualTypes has mode INTERNAl, then namespace resolution for enums is
|
||||
* done (eg for Qt::Alignment).
|
||||
* TODO: If contextualTypes has mode QML, then inline component resolution is done
|
||||
* If contextualTypes has mode QML, then inline component resolution is done
|
||||
* ("qmlFileName.IC" is correctly resolved from qmlFileName).
|
||||
*/
|
||||
static ImportedScope<QQmlJSScope::ConstPtr> findType(const QString &name,
|
||||
|
@ -625,7 +625,6 @@ private:
|
|||
QQmlJSScope() = default;
|
||||
QQmlJSScope(const QQmlJSScope &) = default;
|
||||
QQmlJSScope &operator=(const QQmlJSScope &) = default;
|
||||
|
||||
static QTypeRevision resolveType(
|
||||
const QQmlJSScope::Ptr &self, const ContextualTypes &contextualTypes,
|
||||
QSet<QString> *usedTypes);
|
||||
|
|
|
@ -102,6 +102,7 @@ set(qml_sources
|
|||
inlineComponents.qml
|
||||
repeaterCrash.qml
|
||||
aliases.qml
|
||||
inlineComponentsFromDifferentFiles.qml
|
||||
|
||||
# support types:
|
||||
DefaultPropertySingleChild.qml
|
||||
|
@ -114,6 +115,8 @@ set(qml_sources
|
|||
ComponentWithAlias1.qml
|
||||
ComponentWithAlias2.qml
|
||||
ComponentWithAlias3.qml
|
||||
InlineComponentProvider.qml
|
||||
InlineComponentReexporter.qml
|
||||
|
||||
badFile.qml
|
||||
)
|
||||
|
|
|
@ -0,0 +1,24 @@
|
|||
// Copyright (C) 2021 The Qt Company Ltd.
|
||||
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
|
||||
|
||||
import QtQuick
|
||||
|
||||
Item {
|
||||
component IC1 : Item {
|
||||
property string objName: "IC1"
|
||||
}
|
||||
|
||||
component IC2: QtObject {
|
||||
property string objName: "IC2"
|
||||
}
|
||||
|
||||
enum MyEnum { Jumps, Over, The, Lazy, Dog }
|
||||
|
||||
Item {
|
||||
component IC3: Item {
|
||||
property string objName: "IC3"
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
|
@ -0,0 +1,8 @@
|
|||
import QtQuick
|
||||
|
||||
Item {
|
||||
|
||||
component IC100: InlineComponentProvider.IC1 {
|
||||
objName: "IC100"
|
||||
}
|
||||
}
|
|
@ -0,0 +1,18 @@
|
|||
import QtQuick
|
||||
import QmltcTests as MyModule
|
||||
|
||||
Item {
|
||||
property MyModule.InlineComponentProvider.IC1 fromModule1
|
||||
fromModule1: MyModule.InlineComponentProvider.IC1 {}
|
||||
|
||||
property var fromModule2: MyModule.InlineComponentProvider.IC1 {}
|
||||
|
||||
property InlineComponentProvider.IC1 fromOtherFile1: InlineComponentProvider.IC1 {}
|
||||
property InlineComponentProvider.IC2 fromOtherFile2: InlineComponentProvider.IC2 {}
|
||||
property InlineComponentProvider.IC3 fromOtherFile3: InlineComponentProvider.IC3 {}
|
||||
|
||||
property InlineComponentReexporter.IC100 reExported: InlineComponentReexporter.IC100 {}
|
||||
|
||||
property var looksLikeEnumIsEnum: InlineComponentProvider.Dog
|
||||
property var looksLikeEnumIsInlineComponent: InlineComponentProvider.IC1 {}
|
||||
}
|
|
@ -77,6 +77,8 @@
|
|||
#include "inlinecomponents.h"
|
||||
#include "repeatercrash.h"
|
||||
#include "aliases.h"
|
||||
#include "inlinecomponentsfromdifferentfiles.h"
|
||||
|
||||
#include "testprivateproperty.h"
|
||||
|
||||
// Qt:
|
||||
|
@ -2995,4 +2997,73 @@ void tst_qmltc::aliases()
|
|||
QCOMPARE(fromComponent->property("aliasToOtherFile"), testString);
|
||||
}
|
||||
|
||||
void tst_qmltc::inlineComponentsFromDifferentFiles()
|
||||
{
|
||||
// check that inline components can be imported from different files
|
||||
QQmlEngine e;
|
||||
PREPEND_NAMESPACE(inlineComponentsFromDifferentFiles) createdByQmltc(&e);
|
||||
|
||||
QQmlComponent component(&e);
|
||||
component.loadUrl(QUrl("qrc:/qt/qml/QmltcTests/inlineComponentsFromDifferentFiles.qml"));
|
||||
QVERIFY(!component.isError());
|
||||
QScopedPointer<QObject> createdByComponent(component.create());
|
||||
|
||||
QCOMPARE(createdByQmltc.fromModule1()->objName(), u"IC1"_s);
|
||||
QCOMPARE(createdByComponent->property("fromModule1")
|
||||
.value<QObject *>()
|
||||
->property("objName")
|
||||
.toString(),
|
||||
u"IC1"_s);
|
||||
|
||||
QCOMPARE(createdByQmltc.fromModule2().value<QObject *>()->property("objName").toString(),
|
||||
u"IC1"_s);
|
||||
QCOMPARE(createdByComponent->property("fromModule2")
|
||||
.value<QObject *>()
|
||||
->property("objName")
|
||||
.toString(),
|
||||
u"IC1"_s);
|
||||
|
||||
QCOMPARE(createdByQmltc.fromOtherFile1()->objName(), u"IC1"_s);
|
||||
QCOMPARE(createdByComponent->property("fromOtherFile1")
|
||||
.value<QObject *>()
|
||||
->property("objName")
|
||||
.toString(),
|
||||
u"IC1"_s);
|
||||
|
||||
QCOMPARE(createdByQmltc.fromOtherFile2()->objName(), u"IC2"_s);
|
||||
QCOMPARE(createdByComponent->property("fromOtherFile2")
|
||||
.value<QObject *>()
|
||||
->property("objName")
|
||||
.toString(),
|
||||
u"IC2"_s);
|
||||
|
||||
QCOMPARE(createdByQmltc.fromOtherFile3()->objName(), u"IC3"_s);
|
||||
QCOMPARE(createdByComponent->property("fromOtherFile3")
|
||||
.value<QObject *>()
|
||||
->property("objName")
|
||||
.toString(),
|
||||
u"IC3"_s);
|
||||
|
||||
QCOMPARE(createdByQmltc.reExported()->objName(), u"IC100"_s);
|
||||
QCOMPARE(createdByComponent->property("reExported")
|
||||
.value<QObject *>()
|
||||
->property("objName")
|
||||
.toString(),
|
||||
u"IC100"_s);
|
||||
|
||||
// test how good/bad inline components mix up with enums (they have very similar syntax)
|
||||
// hard code enum values for better test readability
|
||||
const int dog = 4;
|
||||
|
||||
QCOMPARE(createdByQmltc.looksLikeEnumIsEnum(), dog);
|
||||
QCOMPARE(
|
||||
createdByQmltc.looksLikeEnumIsInlineComponent().value<QObject *>()->property("objName"),
|
||||
u"IC1"_s);
|
||||
|
||||
QCOMPARE(createdByComponent->property("looksLikeEnumIsEnum"), dog);
|
||||
QCOMPARE(createdByComponent->property("looksLikeEnumIsInlineComponent")
|
||||
.value<QObject *>()
|
||||
->property("objName"),
|
||||
u"IC1"_s);
|
||||
}
|
||||
QTEST_MAIN(tst_qmltc)
|
||||
|
|
|
@ -89,4 +89,5 @@ private slots:
|
|||
void appendToQQmlListProperty();
|
||||
void inlineComponents();
|
||||
void aliases();
|
||||
void inlineComponentsFromDifferentFiles();
|
||||
};
|
||||
|
|
|
@ -188,7 +188,7 @@ bool QmltcVisitor::visit(QQmlJS::AST::UiObjectDefinition *object)
|
|||
if (!QQmlJSImportVisitor::visit(object))
|
||||
return false;
|
||||
|
||||
if (processingRoot) {
|
||||
if (processingRoot || m_currentScope->isInlineComponent()) {
|
||||
Q_ASSERT(rootScopeIsValid());
|
||||
setRootFilePath();
|
||||
}
|
||||
|
@ -796,9 +796,12 @@ void QmltcVisitor::checkForNamingCollisionsWithCpp(const QQmlJSScope::ConstPtr &
|
|||
// this information in QQmlJSMetaPropertyBinding currently
|
||||
}
|
||||
|
||||
/*! \internal
|
||||
* Sets the file paths for the document and the inline components roots.
|
||||
*/
|
||||
void QmltcVisitor::setRootFilePath()
|
||||
{
|
||||
const QString filePath = m_exportedRootScope->filePath();
|
||||
const QString filePath = m_currentScope->filePath();
|
||||
if (filePath.endsWith(u".h")) // assume the correct path is set
|
||||
return;
|
||||
Q_ASSERT(filePath.endsWith(u".qml"_s));
|
||||
|
@ -816,7 +819,7 @@ void QmltcVisitor::setRootFilePath()
|
|||
return;
|
||||
}
|
||||
// NB: get the file name to avoid prefixes
|
||||
m_exportedRootScope->setFilePath(QFileInfo(*firstHeader).fileName());
|
||||
m_currentScope->setFilePath(QFileInfo(*firstHeader).fileName());
|
||||
}
|
||||
|
||||
QString QmltcVisitor::sourceDirectoryPath(const QString &path)
|
||||
|
|
Loading…
Reference in New Issue