qmllint: Properly warn about calling properties
Previously whenever a property was called the warnings qmllint emitted made it seem like the property was just not found instead of not being callable. Now we will warn if you try to call a property and warn about properties shadowing methods, slots or signals. We also discourage calling variant properties now that may or may not be a function. This change also fixes an assert being hit in isMissingPropertyType due to our previous, lackluster checks. Fixes: QTBUG-101074 Pick-to: 6.3 Change-Id: I0790b4a4584f3430ee1d8ddf549611225a36cc5b Reviewed-by: Ulf Hermann <ulf.hermann@qt.io>
This commit is contained in:
parent
167b22c701
commit
1829d7c64c
|
@ -260,7 +260,7 @@ QQmlJS::SourceLocation QQmlJSTypePropagator::getCurrentBindingSourceLocation() c
|
||||||
return combine(entries.constFirst().location, entries.constLast().location);
|
return combine(entries.constFirst().location, entries.constLast().location);
|
||||||
}
|
}
|
||||||
|
|
||||||
void QQmlJSTypePropagator::handleUnqualifiedAccess(const QString &name) const
|
void QQmlJSTypePropagator::handleUnqualifiedAccess(const QString &name, bool isMethod) const
|
||||||
{
|
{
|
||||||
auto location = getCurrentSourceLocation();
|
auto location = getCurrentSourceLocation();
|
||||||
|
|
||||||
|
@ -271,8 +271,12 @@ void QQmlJSTypePropagator::handleUnqualifiedAccess(const QString &name) const
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isMissingPropertyType(m_function->qmlScope, name))
|
if (isMethod) {
|
||||||
|
if (isCallingProperty(m_function->qmlScope, name))
|
||||||
|
return;
|
||||||
|
} else if (isMissingPropertyType(m_function->qmlScope, name)) {
|
||||||
return;
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
std::optional<FixSuggestion> suggestion;
|
std::optional<FixSuggestion> suggestion;
|
||||||
|
|
||||||
|
@ -462,6 +466,46 @@ bool QQmlJSTypePropagator::isMissingPropertyType(QQmlJSScope::ConstPtr scope,
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool QQmlJSTypePropagator::isCallingProperty(QQmlJSScope::ConstPtr scope, const QString &name) const
|
||||||
|
{
|
||||||
|
auto property = scope->property(name);
|
||||||
|
if (!property.isValid())
|
||||||
|
return false;
|
||||||
|
|
||||||
|
QString propertyType = u"Property"_qs;
|
||||||
|
|
||||||
|
auto methods = scope->methods(name);
|
||||||
|
|
||||||
|
QString errorType;
|
||||||
|
if (!methods.isEmpty()) {
|
||||||
|
errorType = u"shadowed by a property."_qs;
|
||||||
|
switch (methods.first().methodType()) {
|
||||||
|
case QQmlJSMetaMethod::Signal:
|
||||||
|
propertyType = u"Signal"_qs;
|
||||||
|
break;
|
||||||
|
case QQmlJSMetaMethod::Slot:
|
||||||
|
propertyType = u"Slot"_qs;
|
||||||
|
break;
|
||||||
|
case QQmlJSMetaMethod::Method:
|
||||||
|
propertyType = u"Method"_qs;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
} else if (m_typeResolver->equals(property.type(), m_typeResolver->varType())) {
|
||||||
|
errorType =
|
||||||
|
u"a variant property. It may or may not be a method. Use a regular function instead."_qs;
|
||||||
|
} else if (m_typeResolver->equals(property.type(), m_typeResolver->jsValueType())) {
|
||||||
|
errorType =
|
||||||
|
u"a QJSValue property. It may or may not be a method. Use a regular Q_INVOKABLE instead."_qs;
|
||||||
|
} else {
|
||||||
|
errorType = u"not a method"_qs;
|
||||||
|
}
|
||||||
|
|
||||||
|
m_logger->log(u"%1 \"%2\" is %3"_qs.arg(propertyType, name, errorType), Log_Type,
|
||||||
|
getCurrentSourceLocation(), true, true, {});
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
void QQmlJSTypePropagator::generate_LoadQmlContextPropertyLookup(int index)
|
void QQmlJSTypePropagator::generate_LoadQmlContextPropertyLookup(int index)
|
||||||
{
|
{
|
||||||
// LoadQmlContextPropertyLookup does not use accumulatorIn. It always refers to the scope.
|
// LoadQmlContextPropertyLookup does not use accumulatorIn. It always refers to the scope.
|
||||||
|
@ -484,7 +528,7 @@ void QQmlJSTypePropagator::generate_LoadQmlContextPropertyLookup(int index)
|
||||||
|
|
||||||
if (!m_state.accumulatorOut().isValid()) {
|
if (!m_state.accumulatorOut().isValid()) {
|
||||||
setError(u"Cannot access value for name "_qs + name);
|
setError(u"Cannot access value for name "_qs + name);
|
||||||
handleUnqualifiedAccess(name);
|
handleUnqualifiedAccess(name, false);
|
||||||
} else if (m_typeResolver->genericType(m_state.accumulatorOut().storedType()).isNull()) {
|
} else if (m_typeResolver->genericType(m_state.accumulatorOut().storedType()).isNull()) {
|
||||||
// It should really be valid.
|
// It should really be valid.
|
||||||
// We get the generic type from aotContext->loadQmlContextPropertyIdLookup().
|
// We get the generic type from aotContext->loadQmlContextPropertyIdLookup().
|
||||||
|
@ -799,6 +843,9 @@ void QQmlJSTypePropagator::generate_CallProperty(int nameIndex, int base, int ar
|
||||||
setError(u"Type %1 does not have a property %2 for calling"_qs
|
setError(u"Type %1 does not have a property %2 for calling"_qs
|
||||||
.arg(callBase.descriptiveName(), propertyName));
|
.arg(callBase.descriptiveName(), propertyName));
|
||||||
|
|
||||||
|
if (callBase.isType() && isCallingProperty(callBase.type(), propertyName))
|
||||||
|
return;
|
||||||
|
|
||||||
if (isRestricted(propertyName))
|
if (isRestricted(propertyName))
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
@ -962,7 +1009,8 @@ void QQmlJSTypePropagator::propagateScopeLookupCall(const QString &functionName,
|
||||||
setAccumulator(m_typeResolver->globalType(m_typeResolver->jsValueType()));
|
setAccumulator(m_typeResolver->globalType(m_typeResolver->jsValueType()));
|
||||||
|
|
||||||
setError(u"Cannot find function '%1'"_qs.arg(functionName));
|
setError(u"Cannot find function '%1'"_qs.arg(functionName));
|
||||||
handleUnqualifiedAccess(functionName);
|
|
||||||
|
handleUnqualifiedAccess(functionName, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
void QQmlJSTypePropagator::generate_CallGlobalLookup(int index, int argc, int argv)
|
void QQmlJSTypePropagator::generate_CallGlobalLookup(int index, int argc, int argv)
|
||||||
|
|
|
@ -208,9 +208,10 @@ private:
|
||||||
bool needsMorePasses = false;
|
bool needsMorePasses = false;
|
||||||
};
|
};
|
||||||
|
|
||||||
void handleUnqualifiedAccess(const QString &name) const;
|
void handleUnqualifiedAccess(const QString &name, bool isMethod) const;
|
||||||
void checkDeprecated(QQmlJSScope::ConstPtr scope, const QString &name, bool isMethod) const;
|
void checkDeprecated(QQmlJSScope::ConstPtr scope, const QString &name, bool isMethod) const;
|
||||||
bool isRestricted(const QString &propertyName) const;
|
bool isRestricted(const QString &propertyName) const;
|
||||||
|
bool isCallingProperty(QQmlJSScope::ConstPtr scope, const QString &name) const;
|
||||||
bool isMissingPropertyType(QQmlJSScope::ConstPtr scope, const QString &type) const;
|
bool isMissingPropertyType(QQmlJSScope::ConstPtr scope, const QString &type) const;
|
||||||
QQmlJS::SourceLocation getCurrentSourceLocation() const;
|
QQmlJS::SourceLocation getCurrentSourceLocation() const;
|
||||||
QQmlJS::SourceLocation getCurrentBindingSourceLocation() const;
|
QQmlJS::SourceLocation getCurrentBindingSourceLocation() const;
|
||||||
|
|
|
@ -0,0 +1,6 @@
|
||||||
|
import QtQml
|
||||||
|
|
||||||
|
QtObject {
|
||||||
|
function foo() {}
|
||||||
|
Component.onCompleted: Qt.callLater(foo);
|
||||||
|
}
|
|
@ -0,0 +1,7 @@
|
||||||
|
import QtQml
|
||||||
|
|
||||||
|
QtObject {
|
||||||
|
property var foo: () => {}
|
||||||
|
|
||||||
|
Component.onCompleted: foo()
|
||||||
|
}
|
|
@ -0,0 +1,8 @@
|
||||||
|
import QtQuick
|
||||||
|
|
||||||
|
QtObject {
|
||||||
|
function foo() {}
|
||||||
|
property bool foo: false
|
||||||
|
|
||||||
|
Component.onCompleted: foo()
|
||||||
|
}
|
|
@ -0,0 +1,6 @@
|
||||||
|
import QtQuick
|
||||||
|
|
||||||
|
MouseArea {
|
||||||
|
id: mouseArea
|
||||||
|
Component.onCompleted: pressed()
|
||||||
|
}
|
|
@ -0,0 +1,6 @@
|
||||||
|
import QtQuick
|
||||||
|
|
||||||
|
MouseArea {
|
||||||
|
id: mouseArea
|
||||||
|
Component.onCompleted: mouseArea.pressed()
|
||||||
|
}
|
|
@ -0,0 +1,6 @@
|
||||||
|
import QtQml
|
||||||
|
|
||||||
|
ObjectModel {
|
||||||
|
property bool move: false
|
||||||
|
Component.onCompleted: move()
|
||||||
|
}
|
|
@ -862,6 +862,30 @@ void TestQmllint::dirtyQmlCode_data()
|
||||||
<< QStringLiteral("jsVarDeclarationsWriteConst.qml")
|
<< QStringLiteral("jsVarDeclarationsWriteConst.qml")
|
||||||
<< QStringLiteral("Cannot assign to read-only property constProp") << QString()
|
<< QStringLiteral("Cannot assign to read-only property constProp") << QString()
|
||||||
<< QString() << false;
|
<< QString() << false;
|
||||||
|
QTest::newRow("shadowedSignal")
|
||||||
|
<< QStringLiteral("shadowedSignal.qml")
|
||||||
|
<< QStringLiteral("Signal \"pressed\" is shadowed by a property.") << QString()
|
||||||
|
<< QString() << false;
|
||||||
|
QTest::newRow("shadowedSignalWithId")
|
||||||
|
<< QStringLiteral("shadowedSignalWithId.qml")
|
||||||
|
<< QStringLiteral("Signal \"pressed\" is shadowed by a property") << QString()
|
||||||
|
<< QString() << false;
|
||||||
|
QTest::newRow("shadowedSlot") << QStringLiteral("shadowedSlot.qml")
|
||||||
|
<< QStringLiteral("Slot \"move\" is shadowed by a property")
|
||||||
|
<< QString() << QString() << false;
|
||||||
|
QTest::newRow("shadowedMethod") << QStringLiteral("shadowedMethod.qml")
|
||||||
|
<< QStringLiteral("Method \"foo\" is shadowed by a property.")
|
||||||
|
<< QString() << QString() << false;
|
||||||
|
QTest::newRow("callVarProp")
|
||||||
|
<< QStringLiteral("callVarProp.qml")
|
||||||
|
<< QStringLiteral("Property \"foo\" is a variant property. It may or may not be a "
|
||||||
|
"method. Use a regular function instead.")
|
||||||
|
<< QString() << QString() << false;
|
||||||
|
QTest::newRow("callJSValue")
|
||||||
|
<< QStringLiteral("callJSValueProp.qml")
|
||||||
|
<< QStringLiteral("Property \"callLater\" is a QJSValue property. It may or may not be "
|
||||||
|
"a method. Use a regular Q_INVOKABLE instead.")
|
||||||
|
<< QString() << QString() << false;
|
||||||
}
|
}
|
||||||
|
|
||||||
void TestQmllint::dirtyQmlCode()
|
void TestQmllint::dirtyQmlCode()
|
||||||
|
|
Loading…
Reference in New Issue