qmllint: Make "Did you mean" look in extension types as well

Makes sure we also look for suggestions in extension types. Adds two new
methods to QQmlJSScope called properties() and methods() to easily get
all of them across base types and extensions.

Change-Id: I5874c0221bac6d6e317b79146227bf749100f05b
Reviewed-by: Andrei Golubev <andrei.golubev@qt.io>
This commit is contained in:
Maximilian Goldstein 2022-03-11 16:03:18 +01:00
parent 28708434b1
commit 01cde42d5d
6 changed files with 118 additions and 34 deletions

View File

@ -148,6 +148,29 @@ bool QQmlJSScope::hasMethod(const QString &name) const
});
}
/*!
Returns all methods visible from this scope including those of
base types and extensions.
\note Methods that get shadowed are not included and only the
version visible from this scope is contained. Additionally method
overrides are not included either, only the first visible version
of any method is included.
*/
QHash<QString, QQmlJSMetaMethod> QQmlJSScope::methods() const
{
QHash<QString, QQmlJSMetaMethod> results;
searchBaseAndExtensionTypes(this, [&](const QQmlJSScope *scope) {
for (auto it = scope->m_methods.constBegin(); it != scope->m_methods.constEnd(); it++) {
if (!results.contains(it.key()))
results.insert(it.key(), it.value());
}
return false;
});
return results;
}
QList<QQmlJSMetaMethod> QQmlJSScope::methods(const QString &name) const
{
QList<QQmlJSMetaMethod> results;
@ -516,6 +539,27 @@ QQmlJSMetaProperty QQmlJSScope::property(const QString &name) const
return prop;
}
/*!
Returns all properties visible from this scope including those of
base types and extensions.
\note Properties that get shadowed are not included and only the
version visible from this scope is contained.
*/
QHash<QString, QQmlJSMetaProperty> QQmlJSScope::properties() const
{
QHash<QString, QQmlJSMetaProperty> results;
searchBaseAndExtensionTypes(this, [&](const QQmlJSScope *scope) {
for (auto it = scope->m_properties.constBegin(); it != scope->m_properties.constEnd();
it++) {
if (!results.contains(it.key()))
results.insert(it.key(), it.value());
}
return false;
});
return results;
}
QQmlJSScope::ConstPtr QQmlJSScope::ownerOfProperty(const QQmlJSScope::ConstPtr &self,
const QString &name)
{

View File

@ -209,6 +209,7 @@ public:
bool hasOwnMethod(const QString &name) const { return m_methods.contains(name); }
bool hasMethod(const QString &name) const;
QHash<QString, QQmlJSMetaMethod> methods() const;
QList<QQmlJSMetaMethod> methods(const QString &name) const;
QList<QQmlJSMetaMethod> methods(const QString &name, QQmlJSMetaMethod::Type type) const;
@ -271,6 +272,7 @@ public:
bool hasProperty(const QString &name) const;
QQmlJSMetaProperty property(const QString &name) const;
QHash<QString, QQmlJSMetaProperty> properties() const;
void setPropertyLocallyRequired(const QString &name, bool isRequired);
bool isPropertyRequired(const QString &name) const;

View File

@ -377,15 +377,13 @@ void QQmlJSTypePropagator::handleUnqualifiedAccess(const QString &name, bool isM
}
if (!suggestion.has_value()) {
for (QQmlJSScope::ConstPtr baseScope = m_function->qmlScope; !baseScope.isNull();
baseScope = baseScope->baseType()) {
if (auto didYouMean = QQmlJSUtils::didYouMean(
name, baseScope->ownProperties().keys() + baseScope->ownMethods().keys(),
location);
didYouMean.has_value()) {
suggestion = didYouMean;
break;
}
if (auto didYouMean =
QQmlJSUtils::didYouMean(name,
m_function->qmlScope->properties().keys()
+ m_function->qmlScope->methods().keys(),
location);
didYouMean.has_value()) {
suggestion = didYouMean;
}
}
@ -751,15 +749,10 @@ void QQmlJSTypePropagator::propagatePropertyLookup(const QString &propertyName)
std::optional<FixSuggestion> fixSuggestion;
for (QQmlJSScope::ConstPtr baseScope = baseType; !baseScope.isNull();
baseScope = baseScope->baseType()) {
if (auto suggestion =
QQmlJSUtils::didYouMean(propertyName, baseScope->ownProperties().keys(),
getCurrentSourceLocation());
suggestion.has_value()) {
fixSuggestion = suggestion;
break;
}
if (auto suggestion = QQmlJSUtils::didYouMean(propertyName, baseType->properties().keys(),
getCurrentSourceLocation());
suggestion.has_value()) {
fixSuggestion = suggestion;
}
if (!fixSuggestion.has_value()
@ -971,14 +964,12 @@ void QQmlJSTypePropagator::generate_CallProperty(int nameIndex, int base, int ar
std::optional<FixSuggestion> fixSuggestion;
for (QQmlJSScope::ConstPtr baseScope = m_typeResolver->containedType(callBase);
!baseScope.isNull(); baseScope = baseScope->baseType()) {
if (auto suggestion = QQmlJSUtils::didYouMean(
propertyName, baseScope->ownMethods().keys(), getCurrentSourceLocation());
suggestion.has_value()) {
fixSuggestion = suggestion;
break;
}
const auto baseType = m_typeResolver->containedType(callBase);
if (auto suggestion = QQmlJSUtils::didYouMean(propertyName, baseType->methods().keys(),
getCurrentSourceLocation());
suggestion.has_value()) {
fixSuggestion = suggestion;
}
m_logger->log(u"Property \"%1\" not found on type \"%2\""_qs.arg(

View File

@ -0,0 +1,9 @@
import QtQml
QtObject {
property string property_not_shadowed
function method_not_shadowed(foo) {}
property string property_shadowed
function method_shadowed(foo) {}
}

View File

@ -0,0 +1,6 @@
import QtQml
Shadowed {
property int property_shadowed
function method_shadowed() {}
}

View File

@ -77,18 +77,13 @@ class tst_qqmljsscope : public QQmlDataTest
if (!error.message.isEmpty())
return QQmlJSScope::ConstPtr();
const QStringList importPaths = {
QLibraryInfo::path(QLibraryInfo::QmlImportsPath),
dataDirectory(),
};
QQmlJSImporter importer { importPaths, /* resource file mapper */ nullptr };
QQmlJSLogger logger;
logger.setFileName(url);
logger.setCode(sourceCode);
logger.setSilent(true);
QQmlJSImportVisitor visitor(&importer, &logger, dataDirectory());
QQmlJSTypeResolver typeResolver { &importer };
QQmlJSImportVisitor visitor(&m_importer, &logger, dataDirectory());
QQmlJSTypeResolver typeResolver { &m_importer };
typeResolver.init(&visitor, document.program);
return visitor.result();
}
@ -99,9 +94,22 @@ private Q_SLOTS:
void orderedBindings();
void signalCreationDifferences();
void allTypesAvailable();
void shadowing();
public:
tst_qqmljsscope() : QQmlDataTest(QT_QMLTEST_DATADIR) { }
tst_qqmljsscope()
: QQmlDataTest(QT_QMLTEST_DATADIR),
m_importer(
{
QLibraryInfo::path(QLibraryInfo::QmlImportsPath),
dataDirectory(),
},
nullptr)
{
}
private:
QQmlJSImporter m_importer;
};
void tst_qqmljsscope::initTestCase()
@ -178,5 +186,29 @@ void tst_qqmljsscope::allTypesAvailable()
QCOMPARE(types[u"$internal$.QObject"_qs].scope, types[u"QtObject"_qs].scope);
}
void tst_qqmljsscope::shadowing()
{
QQmlJSScope::ConstPtr root = run(u"shadowing.qml"_qs);
QVERIFY(root);
QVERIFY(root->baseType());
// Check whether properties are properly shadowed
const auto properties = root->properties();
QVERIFY(properties.contains(u"property_not_shadowed"_qs));
QVERIFY(properties.contains(u"property_shadowed"_qs));
QCOMPARE(properties[u"property_not_shadowed"_qs].typeName(), u"QString"_qs);
QCOMPARE(properties[u"property_shadowed"_qs].typeName(), u"int"_qs);
// Check whether methods are properly shadowed
const auto methods = root->methods();
QCOMPARE(methods.count(u"method_not_shadowed"_qs), 1);
QCOMPARE(methods.count(u"method_shadowed"_qs), 1);
QCOMPARE(methods[u"method_not_shadowed"_qs].parameterNames().size(), 1);
QCOMPARE(methods[u"method_shadowed"_qs].parameterNames().size(), 0);
}
QTEST_MAIN(tst_qqmljsscope)
#include "tst_qqmljsscope.moc"