qmllint: Do not warn about unnotifiable properties outside bindings

It doesn't cause any harm if we are not inside a binding. There's a
slight issue with the fact that we now don't warn if one extracts the
read into a separate function, and calls the function inside a binding,
but that is still better than spurious warnings.

Fix this by checking in which context the read occurs. We currently rely
on a "magic" name we give to the function's scope if it as a binding,
but this will be fixed in a follow up commit introducing new scope
types. We don't want to do introduce them here, as they would be new API
not suitable for picking back to 6.10.

Another open issue is that the onRead handler gets the outer QML scope
as the context, which necessiates that we traverse its child scopes to
find the actual function. Fixing that would necessiate some larger work
in the QQmlTypePropagator, and is consequently deferred to another
patch, too.

Pick-to: 6.10
Fixes: QTBUG-138346
Change-Id: I29ea39eb32a18d9b54ded8d5e2c9a5f66051374f
Reviewed-by: Ulf Hermann <ulf.hermann@qt.io>
This commit is contained in:
Fabian Kosmale 2025-07-08 12:57:22 +02:00
parent 4985e06e53
commit 740d67fbca
4 changed files with 33 additions and 3 deletions

View File

@ -2486,7 +2486,8 @@ bool QQmlJSImportVisitor::visit(UiScriptBinding *scriptBinding)
leaveEnvironment();
}
enterEnvironment(QQmlSA::ScopeType::JSFunctionScope, QStringLiteral("binding"),
enterEnvironment(QQmlSA::ScopeType::JSFunctionScope,
signal ? u"changeHandler"_s : u"binding"_s,
scriptBinding->statement->firstSourceLocation());
return true;

View File

@ -506,6 +506,10 @@ void QQmlJSLinter::processMessages(QJsonArray &warnings)
});
}
static bool scopeIsBinding(const QQmlJSScope::ConstPtr& scope) {
return scope->scopeType() == QQmlJSScope::ScopeType::JSFunctionScope && scope->baseTypeName() == u"binding";
}
QQmlJSLinter::LintResult QQmlJSLinter::lintFile(const QString &filename,
const QString *fileContents, const bool silent,
QJsonArray *json, const QStringList &qmlImportPaths,
@ -673,15 +677,27 @@ QQmlJSLinter::LintResult QQmlJSLinter::lintFile(const QString &filename,
QQmlSA::PropertyPassBuilder(passMan.get())
.withOnRead([](QQmlSA::PropertyPass *self, const QQmlSA::Element &element,
const QString &propName, const QQmlSA::Element &readScope,
const QString &propName, const QQmlSA::Element &readScope_,
QQmlSA::SourceLocation location) {
Q_UNUSED(readScope);
const auto &elementScope = QQmlJSScope::scope(element);
const auto &owner = QQmlJSScope::ownerOfProperty(elementScope, propName).scope;
if (!owner || owner->isComposite() || owner->isValueType())
return;
const auto &prop = QQmlSA::PropertyPrivate::property(element.property(propName));
if (prop.index() != -1 && !prop.isPropertyConstant() && prop.notify().isEmpty()) {
const QQmlJSScope::ConstPtr &readScope = QQmlJSScope::scope(readScope_);
// FIXME: we currently get the closest QML Scope as readScope, instead of
// the innermost scope. We try locate it here via source location
Q_ASSERT(readScope->scopeType() == QQmlJSScope::ScopeType::QMLScope);
for (auto it = readScope->childScopesBegin(); it != readScope->childScopesEnd(); ++it) {
QQmlJS::SourceLocation childLocation = (*it)->sourceLocation();
if ( childLocation.offset <= location.offset() &&
(childLocation.offset + childLocation.length <= location.offset() + location.length()) ) {
if (!scopeIsBinding(*it))
return;
}
}
const QString msg =
"Reading non-constant and non-notifiable property %1. "_L1
"Binding might not update when the property changes."_L1.arg(propName);

View File

@ -0,0 +1,12 @@
import QtQuick
Item {
DropArea {
onDropped: (drop) => {
console.log(drop.hasUrls)
}
}
function f(drop: DragEvent) {
console.log(drop.hasUrls)
}
}

View File

@ -2072,6 +2072,7 @@ void TestQmllint::cleanQmlCode_data()
QTest::newRow("uiQml") << QStringLiteral("FormUser.qml");
QTest::newRow("unexportedCppBase") << QStringLiteral("unexportedCppBase.qml");
QTest::newRow("unknownBuiltinFont") << QStringLiteral("ButtonLoader.qml");
QTest::newRow("unnotifiableReadOutsideBinding") << QStringLiteral("unnotifiableReadOutsideBinding.qml");
QTest::newRow("v4SequenceMethods") << QStringLiteral("v4SequenceMethods.qml");
QTest::newRow("valueSource") << QStringLiteral("valueSource.qml");
QTest::newRow("var") << QStringLiteral("var.qml");