qmlcompiler: Fully support required properties

Previously support of required properties was limited to detecting whether a property that was required actually exists. Now it also enables us to determine whether or not the required property was ever bound to.
Still limited by the fact we do not fully support script bindings yet.

Fixes: QTBUG-86755
Pick-to: 6.2
Change-Id: I1abb921d3b4f86a7929f0f829b541088e0c2bf60
Reviewed-by: Ulf Hermann <ulf.hermann@qt.io>
Reviewed-by: Fabian Kosmale <fabian.kosmale@qt.io>
This commit is contained in:
Maximilian Goldstein 2021-06-11 14:32:31 +02:00
parent 906ccb4694
commit 803a5fda05
9 changed files with 146 additions and 8 deletions

View File

@ -40,7 +40,7 @@
import QML
QtObject {
required property string name
required property string type
property string name
property string type
property bool isPointer: false
}

View File

@ -40,7 +40,7 @@
import QML
Member {
required property string type
property string type
property bool isPointer: false
property bool isReadonly: false
property bool isRequired: false

View File

@ -300,7 +300,7 @@ void QQmlJSImportVisitor::endVisit(UiProgram *)
resolveAliases();
processDefaultProperties();
processPropertyTypes();
checkPropertyBindings();
processPropertyBindings();
checkSignals();
processPropertyBindingObjects();
checkRequiredProperties();
@ -589,9 +589,58 @@ void QQmlJSImportVisitor::checkRequiredProperties()
Log_Required, required.location);
}
}
for (const auto &defScope : m_objectDefinitionScopes) {
if (defScope->parentScope() == m_globalScope || defScope->isInlineComponent())
continue;
QVector<QQmlJSScope::ConstPtr> scopesToSearch;
for (QQmlJSScope::ConstPtr scope = defScope; scope; scope = scope->baseType()) {
scopesToSearch << scope;
const auto ownProperties = scope->ownProperties();
for (auto propertyIt = ownProperties.constBegin();
propertyIt != ownProperties.constEnd(); ++propertyIt) {
const QString propName = propertyIt.key();
QQmlJSScope::ConstPtr prevRequiredScope;
for (QQmlJSScope::ConstPtr requiredScope : scopesToSearch) {
if (requiredScope->isPropertyLocallyRequired(propName)) {
bool found =
std::find_if(scopesToSearch.constBegin(), scopesToSearch.constEnd(),
[&](QQmlJSScope::ConstPtr scope) {
return scope->hasPropertyBinding(propName);
})
!= scopesToSearch.constEnd();
if (!found) {
const QString propertyScopeName = scopesToSearch.length() > 1
? getScopeName(scopesToSearch.at(scopesToSearch.length() - 2),
QQmlJSScope::QMLScope)
: u"here"_qs;
const QString requiredScopeName = prevRequiredScope
? getScopeName(prevRequiredScope, QQmlJSScope::QMLScope)
: u"here"_qs;
QString message =
QStringLiteral(
"Component is missing required property %1 from %2")
.arg(propName)
.arg(propertyScopeName);
if (requiredScope != scope)
message += QStringLiteral(" (marked as required by %3)")
.arg(requiredScopeName);
m_logger.log(message, Log_Required, defScope->sourceLocation());
}
}
prevRequiredScope = requiredScope;
}
}
}
}
}
void QQmlJSImportVisitor::checkPropertyBindings()
void QQmlJSImportVisitor::processPropertyBindings()
{
for (auto it = m_propertyBindings.constBegin(); it != m_propertyBindings.constEnd(); ++it) {
QQmlJSScope::Ptr propertyScope = it.key();
@ -639,6 +688,12 @@ void QQmlJSImportVisitor::checkPropertyBindings()
m_logger.log(message, Log_Deprecation, propertyScope->sourceLocation());
}
QQmlJSMetaPropertyBinding binding(property);
// TODO: Actually store the value
scope->addOwnPropertyBinding(binding);
}
}
}

View File

@ -164,7 +164,7 @@ protected:
QVector<QQmlJSAnnotation> parseAnnotations(QQmlJS::AST::UiAnnotationList *list);
void addDefaultProperties();
void processDefaultProperties();
void checkPropertyBindings();
void processPropertyBindings();
void checkRequiredProperties();
void processPropertyTypes();
void processPropertyBindingObjects();

View File

@ -3,4 +3,5 @@ import QtQml 2.15
QtObject {
property int x
required x
x: 5
}

View File

@ -0,0 +1,24 @@
import QtQuick 2.0
Item {
component Base : Item {
required property string required_now_string
property string required_later_string
required property QtObject required_now_object
property QtObject required_later_object
}
component Derived : Base {
required required_later_string
required required_later_object
}
Derived {
required_now_string: ""
required_later_string: ""
required_now_object: QtObject {}
required_later_object: QtObject {}
}
}

View File

@ -0,0 +1,18 @@
import QtQuick 2.0
Item {
component Base : Item {
required property string required_now_string
property string required_later_string
property string required_even_later_string
}
component Derived : Base {
required required_later_string
}
Derived {
required required_even_later_string
required_now_string: ""
}
}

View File

@ -0,0 +1,17 @@
import QtQuick 2.0
Item {
component Base : Item {
required property string required_now_string
property string required_later_string
}
component Derived : Base {
required required_later_string
}
Derived {
required property string required_defined_here_string
required_later_string: ""
}
}

View File

@ -828,8 +828,31 @@ void TestQmllint::requiredProperty()
{
QVERIFY(runQmllint("requiredProperty.qml", true).isEmpty());
{
const QString errors = runQmllint("requiredMissingProperty.qml", false);
QVERIFY(errors.contains(QStringLiteral("Property \"foo\" was marked as required but does not exist.")));
QVERIFY(errors.contains(
QStringLiteral("Property \"foo\" was marked as required but does not exist.")));
}
QVERIFY(runQmllint("requiredPropertyBindings.qml", true).isEmpty());
{
const QString errors = runQmllint("requiredPropertyBindingsNow.qml", false);
QVERIFY(errors.contains(QStringLiteral(
"Component is missing required property required_now_string from Base")));
QVERIFY(errors.contains(QStringLiteral(
"Component is missing required property required_defined_here_string from here")));
}
{
const QString errors = runQmllint("requiredPropertyBindingsLater.qml", false);
QVERIFY(errors.contains(
QStringLiteral("Component is missing required property required_later_string from "
"Base (marked as required by Derived)")));
QVERIFY(errors.contains(
QStringLiteral("Component is missing required property required_even_later_string "
"from Base (marked as required by here)")));
}
}
void TestQmllint::settingsFile()