diff --git a/src/qmlcompiler/qqmljsimportvisitor.cpp b/src/qmlcompiler/qqmljsimportvisitor.cpp index 4df6a2f7be..b6f944086d 100644 --- a/src/qmlcompiler/qqmljsimportvisitor.cpp +++ b/src/qmlcompiler/qqmljsimportvisitor.cpp @@ -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; diff --git a/src/qmlcompiler/qqmljslinter.cpp b/src/qmlcompiler/qqmljslinter.cpp index b74e17bb9d..1b7c454f79 100644 --- a/src/qmlcompiler/qqmljslinter.cpp +++ b/src/qmlcompiler/qqmljslinter.cpp @@ -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); diff --git a/tests/auto/qml/qmllint/data/unnotifiableReadOutsideBinding.qml b/tests/auto/qml/qmllint/data/unnotifiableReadOutsideBinding.qml new file mode 100644 index 0000000000..3271876d83 --- /dev/null +++ b/tests/auto/qml/qmllint/data/unnotifiableReadOutsideBinding.qml @@ -0,0 +1,12 @@ +import QtQuick +Item { + DropArea { + onDropped: (drop) => { + console.log(drop.hasUrls) + } + } + + function f(drop: DragEvent) { + console.log(drop.hasUrls) + } +} diff --git a/tests/auto/qml/qmllint/tst_qmllint.cpp b/tests/auto/qml/qmllint/tst_qmllint.cpp index a9ba69cd36..e85319d370 100644 --- a/tests/auto/qml/qmllint/tst_qmllint.cpp +++ b/tests/auto/qml/qmllint/tst_qmllint.cpp @@ -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");