aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorMaximilian Goldstein <max.goldstein@qt.io>2022-05-02 16:51:01 +0200
committerMaximilian Goldstein <max.goldstein@qt.io>2022-05-16 18:22:45 +0200
commit2ebee301fd6629f2d5c604fd021c61c15692775f (patch)
tree19a0835b68243cdcb845062840229fff2fe89872
parent8c3b77da082b11d4b5839276d655e7f2540f1c32 (diff)
Reimplement PropertyPass to evaluate bindings
This change reimplements the PropertyPass to be based on bindings rather than on just iterating over elements. This is a lot less cost intensive than iterating over all properties regardless of whether they are actually used. You may still achieve the same thing with the more flexible element pass, just with the benefit that you can choose what properties you want to iterate over instead of iterating over all of them. To demonstrate the passes usefulness the existing attached property warnings are ported to use the binding pass and can now also warn when an attached property is read or written in a context where it's not supposed to be used. Fixes: QTBUG-102860 Fixes: QTBUG-102418 Task-number: QTBUG-102859 Change-Id: Iea87a1b05b954429b8bf00fd27b60487940af679 Reviewed-by: Ulf Hermann <ulf.hermann@qt.io> Reviewed-by: Qt CI Bot <qt_ci_bot@qt-project.org>
-rw-r--r--src/plugins/qmllint/quick/quicklintplugin.cpp178
-rw-r--r--src/plugins/qmllint/quick/quicklintplugin.h20
-rw-r--r--src/qmlcompiler/qqmljscompilepass_p.h1
-rw-r--r--src/qmlcompiler/qqmljsfunctioninitializer.cpp7
-rw-r--r--src/qmlcompiler/qqmljslinter.cpp11
-rw-r--r--src/qmlcompiler/qqmljslintercodegen.cpp3
-rw-r--r--src/qmlcompiler/qqmljslintercodegen_p.h9
-rw-r--r--src/qmlcompiler/qqmljstypepropagator.cpp42
-rw-r--r--src/qmlcompiler/qqmljstypepropagator_p.h9
-rw-r--r--src/qmlcompiler/qqmlsa.cpp232
-rw-r--r--src/qmlcompiler/qqmlsa_p.h83
-rw-r--r--tests/auto/qml/qmllint/data/pluginQuick_attached.qml6
-rw-r--r--tests/auto/qml/qmllint/data/pluginQuick_attachedClean.qml31
-rw-r--r--tests/auto/qml/qmllint/data/propertypass_pluginTest.qml24
-rw-r--r--tests/auto/qml/qmllint/lintplugin.cpp74
-rw-r--r--tests/auto/qml/qmllint/tst_qmllint.cpp66
16 files changed, 592 insertions, 204 deletions
diff --git a/src/plugins/qmllint/quick/quicklintplugin.cpp b/src/plugins/qmllint/quick/quicklintplugin.cpp
index d021479846..8a301e5ffc 100644
--- a/src/plugins/qmllint/quick/quicklintplugin.cpp
+++ b/src/plugins/qmllint/quick/quicklintplugin.cpp
@@ -81,57 +81,98 @@ void ForbiddenChildrenPropertyValidatorPass::run(const QQmlSA::Element &element)
}
AttachedPropertyTypeValidatorPass::AttachedPropertyTypeValidatorPass(QQmlSA::PassManager *manager)
- : QQmlSA::ElementPass(manager)
+ : QQmlSA::PropertyPass(manager)
{
}
-void AttachedPropertyTypeValidatorPass::addWarning(
- QAnyStringView attachedTypeName,
- QList<AttachedPropertyTypeValidatorPass::TypeDescription> allowedTypes,
- QAnyStringView warning)
+QString AttachedPropertyTypeValidatorPass::addWarning(TypeDescription attachType,
+ QList<TypeDescription> allowedTypes,
+ bool allowInDelegate, QAnyStringView warning)
{
- AttachedPropertyTypeValidatorPass::Warning warningInfo;
- warningInfo.message = warning.toString();
+ QVarLengthArray<QQmlSA::Element, 4> elements;
+
+ const QQmlSA::Element baseType = resolveType(attachType.module, attachType.name);
- for (const TypeDescription &description : allowedTypes) {
- auto type = resolveType(description.module, description.name);
+ QString typeName = baseType->attachedTypeName();
+ for (const TypeDescription &desc : allowedTypes) {
+ const QQmlSA::Element type = resolveType(desc.module, desc.name);
if (type.isNull())
continue;
-
- warningInfo.allowedTypes.push_back(type);
+ elements.push_back(type);
}
- m_attachedTypes[attachedTypeName.toString()] = warningInfo;
+ m_attachedTypes.insert({ std::make_pair<>(
+ typeName, Warning { elements, allowInDelegate, warning.toString() }) });
+
+ return typeName;
}
-bool AttachedPropertyTypeValidatorPass::shouldRun(const QQmlSA::Element &element)
+void AttachedPropertyTypeValidatorPass::checkWarnings(const QQmlSA::Element &element,
+ const QQmlSA::Element &scopeUsedIn,
+ const QQmlJS::SourceLocation &location)
{
- for (const auto pair : m_attachedTypes.asKeyValueRange()) {
- if (element->hasOwnPropertyBindings(pair.first))
- return true;
+ auto warning = m_attachedTypes.constFind(element->internalName());
+ if (warning == m_attachedTypes.cend())
+ return;
+ for (const QQmlSA::Element &type : warning->allowedTypes) {
+ if (scopeUsedIn->inherits(type))
+ return;
}
- return false;
-}
-
-void AttachedPropertyTypeValidatorPass::run(const QQmlSA::Element &element)
-{
- for (const auto pair : m_attachedTypes.asKeyValueRange()) {
- if (element->hasOwnPropertyBindings(pair.first)) {
- bool hasAllowedType = false;
- for (const QQmlSA::Element &type : pair.second.allowedTypes) {
- if (element->inherits(type)) {
- hasAllowedType = true;
- break;
- }
- }
- if (!hasAllowedType) {
- auto binding = *element->ownPropertyBindings(pair.first).first;
- emitWarning(pair.second.message, binding.sourceLocation());
+ if (warning->allowInDelegate) {
+ if (scopeUsedIn->isPropertyRequired(u"index"_s)
+ || scopeUsedIn->isPropertyRequired(u"model"_s))
+ return;
+ if (scopeUsedIn->parentScope()) {
+ for (const QQmlJSMetaPropertyBinding &binding :
+ scopeUsedIn->parentScope()->propertyBindings(u"delegate"_s)) {
+ if (!binding.hasObject())
+ continue;
+ if (binding.objectType() == scopeUsedIn)
+ return;
}
}
}
+
+ emitWarning(warning->message, location);
+}
+
+void AttachedPropertyTypeValidatorPass::onBinding(const QQmlSA::Element &element,
+ const QString &propertyName,
+ const QQmlJSMetaPropertyBinding &binding,
+ const QQmlSA::Element &bindingScope,
+ const QQmlSA::Element &value)
+{
+ Q_UNUSED(element)
+ Q_UNUSED(propertyName)
+ Q_UNUSED(bindingScope)
+ Q_UNUSED(value)
+
+ checkWarnings(bindingScope->baseType(), element, binding.sourceLocation());
+}
+
+void AttachedPropertyTypeValidatorPass::onRead(const QQmlSA::Element &element,
+ const QString &propertyName,
+ const QQmlSA::Element &readScope,
+ QQmlJS::SourceLocation location)
+{
+ Q_UNUSED(readScope)
+ Q_UNUSED(propertyName)
+
+ checkWarnings(element, readScope, location);
+}
+
+void AttachedPropertyTypeValidatorPass::onWrite(const QQmlSA::Element &element,
+ const QString &propertyName,
+ const QQmlSA::Element &value,
+ const QQmlSA::Element &writeScope,
+ QQmlJS::SourceLocation location)
+{
+ Q_UNUSED(propertyName)
+ Q_UNUSED(value)
+
+ checkWarnings(element, writeScope, location);
}
ControlsNativeValidatorPass::ControlsNativeValidatorPass(QQmlSA::PassManager *manager)
@@ -441,40 +482,59 @@ void QmlLintQuickPlugin::registerPasses(QQmlSA::PassManager *manager,
manager->registerElementPass(std::move(forbiddenChildProperty));
}
- auto attachedPropertyType = std::make_unique<AttachedPropertyTypeValidatorPass>(manager);
+ auto attachedPropertyType = std::make_shared<AttachedPropertyTypeValidatorPass>(manager);
+
+ auto addAttachedWarning =
+ [&](AttachedPropertyTypeValidatorPass::TypeDescription attachedType,
+ QList<AttachedPropertyTypeValidatorPass::TypeDescription> allowedTypes,
+ QAnyStringView warning, bool allowInDelegate = false) {
+ QString attachedTypeName = attachedPropertyType->addWarning(
+ attachedType, allowedTypes, allowInDelegate, warning);
+ manager->registerPropertyPass(attachedPropertyType, attachedType.module,
+ u"$internal$."_s + attachedTypeName);
+ };
if (hasQuick) {
- attachedPropertyType->addWarning("Accessible", { { "QtQuick", "Item" } },
- "Accessible must be attached to an Item");
- attachedPropertyType->addWarning(
- "LayoutMirroring", { { "QtQuick", "Item" }, { "QtQuick", "Window" } },
- "LayoutDirection attached property only works with Items and Windows");
- attachedPropertyType->addWarning("EnterKey", { { "QtQuick", "Item" } },
- "EnterKey attached property only works with Items");
+ addAttachedWarning({ "QtQuick", "Accessible" }, { { "QtQuick", "Item" } },
+ "Accessible must be attached to an Item");
+ addAttachedWarning({ "QtQuick", "LayoutMirroring" },
+ { { "QtQuick", "Item" }, { "QtQuick", "Window" } },
+ "LayoutDirection attached property only works with Items and Windows");
+ addAttachedWarning({ "QtQuick", "EnterKey" }, { { "QtQuick", "Item" } },
+ "EnterKey attached property only works with Items");
}
-
if (hasQuickLayouts) {
- attachedPropertyType->addWarning("Layout", { { "QtQuick", "Item" } },
- "Layout must be attached to Item elements");
+ addAttachedWarning({ "QtQuick.Layouts", "Layout" }, { { "QtQuick", "Item" } },
+ "Layout must be attached to Item elements");
+ addAttachedWarning({ "QtQuick.Layouts", "StackLayout" }, { { "QtQuick", "Item" } },
+ "StackLayout must be attached to an Item");
}
-
if (hasQuickControls) {
- attachedPropertyType->addWarning(
- "ScrollBar", { { "QtQuick", "Flickable" }, { "QtQuick.Templates", "ScrollView" } },
- "ScrollBar must be attached to a Flickable or ScrollView");
- attachedPropertyType->addWarning("ScrollIndicator", { { "QtQuick", "Flickable" } },
- "ScrollIndicator must be attached to a Flickable");
- attachedPropertyType->addWarning("SplitView", { { "QtQuick", "Item" } },
- "SplitView attached property only works with Items");
- attachedPropertyType->addWarning("StackView", { { "QtQuick", "Item" } },
- "StackView attached property only works with Items");
- attachedPropertyType->addWarning("ToolTip", { { "QtQuick", "Item" } },
- "ToolTip must be attached to an Item");
-
manager->registerElementPass(std::make_unique<ControlsSwipeDelegateValidatorPass>(manager));
- }
- manager->registerElementPass(std::move(attachedPropertyType));
+ addAttachedWarning({ "QtQuick.Templates", "ScrollBar" },
+ { { "QtQuick", "Flickable" }, { "QtQuick.Templates", "ScrollView" } },
+ "ScrollBar must be attached to a Flickable or ScrollView");
+ addAttachedWarning({ "QtQuick.Templates", "ScrollIndicator" },
+ { { "QtQuick", "Flickable" } },
+ "ScrollIndicator must be attached to a Flickable");
+ addAttachedWarning({ "QtQuick.Templates", "TextArea" }, { { "QtQuick", "Flickable" } },
+ "TextArea must be attached to a Flickable");
+ addAttachedWarning({ "QtQuick.Templates", "SplitView" }, { { "QtQuick", "Item" } },
+ "SplitView attached property only works with Items");
+ addAttachedWarning({ "QtQuick.Templates", "StackView" }, { { "QtQuick", "Item" } },
+ "StackView attached property only works with Items");
+ addAttachedWarning({ "QtQuick.Templates", "ToolTip" }, { { "QtQuick", "Item" } },
+ "ToolTip must be attached to an Item");
+ addAttachedWarning({ "QtQuick.Templates", "SwipeDelegate" }, { { "QtQuick", "Item" } },
+ "Attached properties of SwipeDelegate must be accessed through an Item");
+ addAttachedWarning({ "QtQuick.Templates", "SwipeView" }, { { "QtQuick", "Item" } },
+ "SwipeView must be attached to an Item");
+ addAttachedWarning(
+ { "QtQuick.Templates", "Tumbler" }, { { "QtQuick", "Tumbler" } },
+ "Tumbler: attached properties of Tumbler must be accessed through a delegate item",
+ true);
+ }
if (manager->hasImportedModule(u"QtQuick.Controls.macOS"_s)
|| manager->hasImportedModule(u"QtQuick.Controls.Windows"_s))
diff --git a/src/plugins/qmllint/quick/quicklintplugin.h b/src/plugins/qmllint/quick/quicklintplugin.h
index 64e6c94a9a..7ceef9a24a 100644
--- a/src/plugins/qmllint/quick/quicklintplugin.h
+++ b/src/plugins/qmllint/quick/quicklintplugin.h
@@ -68,7 +68,7 @@ private:
QHash<QQmlSA::Element, QVarLengthArray<Warning, 8>> m_types;
};
-class AttachedPropertyTypeValidatorPass : public QQmlSA::ElementPass
+class AttachedPropertyTypeValidatorPass : public QQmlSA::PropertyPass
{
public:
struct TypeDescription
@@ -79,16 +79,26 @@ public:
AttachedPropertyTypeValidatorPass(QQmlSA::PassManager *manager);
- void addWarning(QAnyStringView attachedTypeName, QList<TypeDescription> allowedTypes,
- QAnyStringView warning);
+ QString addWarning(TypeDescription attachType, QList<TypeDescription> allowedTypes,
+ bool allowInDelegate, QAnyStringView warning);
- bool shouldRun(const QQmlSA::Element &element) override;
- void run(const QQmlSA::Element &element) override;
+ void onBinding(const QQmlSA::Element &element, const QString &propertyName,
+ const QQmlJSMetaPropertyBinding &binding, const QQmlSA::Element &bindingScope,
+ const QQmlSA::Element &value) override;
+ void onRead(const QQmlSA::Element &element, const QString &propertyName,
+ const QQmlSA::Element &readScope, QQmlJS::SourceLocation location) override;
+ void onWrite(const QQmlSA::Element &element, const QString &propertyName,
+ const QQmlSA::Element &value, const QQmlSA::Element &writeScope,
+ QQmlJS::SourceLocation location) override;
private:
+ void checkWarnings(const QQmlSA::Element &element, const QQmlSA::Element &scopeUsedIn,
+ const QQmlJS::SourceLocation &location);
+
struct Warning
{
QVarLengthArray<QQmlSA::Element, 4> allowedTypes;
+ bool allowInDelegate = false;
QString message;
};
QHash<QString, Warning> m_attachedTypes;
diff --git a/src/qmlcompiler/qqmljscompilepass_p.h b/src/qmlcompiler/qqmljscompilepass_p.h
index c16527e286..d395059c1b 100644
--- a/src/qmlcompiler/qqmljscompilepass_p.h
+++ b/src/qmlcompiler/qqmljscompilepass_p.h
@@ -91,6 +91,7 @@ public:
const SourceLocationTable *sourceLocations = nullptr;
bool isSignalHandler = false;
bool isQPropertyBinding = false;
+ bool isProperty = false;
};
struct State
diff --git a/src/qmlcompiler/qqmljsfunctioninitializer.cpp b/src/qmlcompiler/qqmljsfunctioninitializer.cpp
index 91a6de790d..2fd9ff6f57 100644
--- a/src/qmlcompiler/qqmljsfunctioninitializer.cpp
+++ b/src/qmlcompiler/qqmljsfunctioninitializer.cpp
@@ -156,8 +156,8 @@ QQmlJSCompilePass::Function QQmlJSFunctionInitializer::run(
QtDebugMsg, bindingLocation, error);
}
- const bool isProperty = m_objectType->hasProperty(propertyName);
- if (!isProperty && QmlIR::IRBuilder::isSignalPropertyName(propertyName)) {
+ function.isProperty = m_objectType->hasProperty(propertyName);
+ if (!function.isProperty && QmlIR::IRBuilder::isSignalPropertyName(propertyName)) {
const QString signalName = QmlIR::IRBuilder::signalNameFromSignalPropertyName(propertyName);
if (signalName.endsWith(u"Changed"_s)
@@ -180,9 +180,8 @@ QQmlJSCompilePass::Function QQmlJSFunctionInitializer::run(
}
}
-
if (!function.isSignalHandler) {
- if (!isProperty) {
+ if (!function.isProperty) {
diagnose(u"Could not compile binding for %1: The property does not exist"_s.arg(
propertyName),
QtWarningMsg, bindingLocation, error);
diff --git a/src/qmlcompiler/qqmljslinter.cpp b/src/qmlcompiler/qqmljslinter.cpp
index ab78b28f66..8f475790e1 100644
--- a/src/qmlcompiler/qqmljslinter.cpp
+++ b/src/qmlcompiler/qqmljslinter.cpp
@@ -41,6 +41,7 @@
#include <QtCore/qlibraryinfo.h>
#include <QtCore/qdir.h>
#include <QtCore/private/qduplicatetracker_p.h>
+#include <QtCore/qscopedpointer.h>
#include <QtQmlCompiler/private/qqmlsa_p.h>
@@ -504,8 +505,10 @@ QQmlJSLinter::LintResult QQmlJSLinter::lintFile(const QString &filename,
QQmlJSLiteralBindingCheck literalCheck;
literalCheck.run(&v, &typeResolver);
+ QScopedPointer<QQmlSA::PassManager> passMan;
+
if (m_enablePlugins) {
- QQmlSA::PassManager passMan(&v, &typeResolver);
+ passMan.reset(new QQmlSA::PassManager(&v, &typeResolver));
for (const Plugin &plugin : m_plugins) {
if (!plugin.isValid() || !plugin.isEnabled())
@@ -513,10 +516,10 @@ QQmlJSLinter::LintResult QQmlJSLinter::lintFile(const QString &filename,
QQmlSA::LintPlugin *instance = plugin.m_instance;
Q_ASSERT(instance);
- instance->registerPasses(&passMan, v.result());
+ instance->registerPasses(passMan.get(), v.result());
}
- passMan.analyze(v.result());
+ passMan->analyze(v.result());
}
success = !m_logger->hasWarnings() && !m_logger->hasErrors();
@@ -537,6 +540,8 @@ QQmlJSLinter::LintResult QQmlJSLinter::lintFile(const QString &filename,
QQmlJSLinterCodegen codegen { &m_importer, resolvedPath, qmldirFiles, m_logger.get(),
&typeInfo };
codegen.setTypeResolver(std::move(typeResolver));
+ if (passMan)
+ codegen.setPassManager(passMan.get());
QQmlJSSaveFunction saveFunction = [](const QV4::CompiledData::SaveableUnitPointer &,
const QQmlJSAotFunctionMap &,
QString *) { return true; };
diff --git a/src/qmlcompiler/qqmljslintercodegen.cpp b/src/qmlcompiler/qqmljslintercodegen.cpp
index fd22e95ef9..6e8472d385 100644
--- a/src/qmlcompiler/qqmljslintercodegen.cpp
+++ b/src/qmlcompiler/qqmljslintercodegen.cpp
@@ -108,7 +108,8 @@ bool QQmlJSLinterCodegen::analyzeFunction(const QV4::Compiler::Context *context,
QQmlJSCompilePass::Function *function,
QQmlJS::DiagnosticMessage *error)
{
- QQmlJSTypePropagator propagator(m_unitGenerator, &m_typeResolver, m_logger, m_typeInfo);
+ QQmlJSTypePropagator propagator(m_unitGenerator, &m_typeResolver, m_logger, m_typeInfo,
+ m_passManager);
QQmlJSCompilePass::InstructionAnnotations annotations = propagator.run(function, error);
if (!error->isValid()) {
QQmlJSShadowCheck shadowCheck(m_unitGenerator, &m_typeResolver, m_logger);
diff --git a/src/qmlcompiler/qqmljslintercodegen_p.h b/src/qmlcompiler/qqmljslintercodegen_p.h
index 53a7c04c89..c23a6fb34d 100644
--- a/src/qmlcompiler/qqmljslintercodegen_p.h
+++ b/src/qmlcompiler/qqmljslintercodegen_p.h
@@ -56,6 +56,10 @@
QT_BEGIN_NAMESPACE
+namespace QQmlSA {
+class PassManager;
+};
+
class QQmlJSLinterCodegen : public QQmlJSAotCompiler
{
public:
@@ -77,8 +81,13 @@ public:
QQmlJSTypeResolver *typeResolver() { return &m_typeResolver; }
+ void setPassManager(QQmlSA::PassManager *passManager) { m_passManager = passManager; }
+
+ QQmlSA::PassManager *passManager() { return m_passManager; }
+
private:
QQmlJSTypeInfo *m_typeInfo;
+ QQmlSA::PassManager *m_passManager = nullptr;
bool analyzeFunction(const QV4::Compiler::Context *context,
QQmlJSCompilePass::Function *function, QQmlJS::DiagnosticMessage *error);
diff --git a/src/qmlcompiler/qqmljstypepropagator.cpp b/src/qmlcompiler/qqmljstypepropagator.cpp
index e5e0bb367c..cc733ec283 100644
--- a/src/qmlcompiler/qqmljstypepropagator.cpp
+++ b/src/qmlcompiler/qqmljstypepropagator.cpp
@@ -29,6 +29,7 @@
#include "qqmljstypepropagator_p.h"
#include "qqmljsutils_p.h"
+#include "qqmlsa_p.h"
#include <private/qv4compilerscanfunctions_p.h>
@@ -48,9 +49,12 @@ using namespace Qt::StringLiterals;
*/
QQmlJSTypePropagator::QQmlJSTypePropagator(const QV4::Compiler::JSUnitGenerator *unitGenerator,
- const QQmlJSTypeResolver *typeResolver, QQmlJSLogger *logger,
- QQmlJSTypeInfo *typeInfo)
- : QQmlJSCompilePass(unitGenerator, typeResolver, logger), m_typeInfo(typeInfo)
+ const QQmlJSTypeResolver *typeResolver,
+ QQmlJSLogger *logger, QQmlJSTypeInfo *typeInfo,
+ QQmlSA::PassManager *passManager)
+ : QQmlJSCompilePass(unitGenerator, typeResolver, logger),
+ m_typeInfo(typeInfo),
+ m_passManager(passManager)
{
}
@@ -121,6 +125,12 @@ void QQmlJSTypePropagator::generate_Ret()
addReadAccumulator(m_returnType);
}
+ if (m_passManager != nullptr && m_function->isProperty) {
+ m_passManager->analyzeBinding(m_function->qmlScope,
+ m_typeResolver->containedType(m_state.accumulatorIn()),
+ getCurrentBindingSourceLocation());
+ }
+
m_state.setHasSideEffects(true);
m_state.skipInstructionsUntilNextJumpTarget = true;
}
@@ -557,6 +567,9 @@ void QQmlJSTypePropagator::generate_LoadQmlContextPropertyLookup(int index)
// It should really be valid.
// We get the generic type from aotContext->loadQmlContextPropertyIdLookup().
setError(u"Cannot determine generic type for "_s + name);
+ } else if (m_passManager != nullptr) {
+ m_passManager->analyzeRead(m_function->qmlScope, name, m_function->qmlScope,
+ getCurrentSourceLocation());
}
}
@@ -589,6 +602,12 @@ void QQmlJSTypePropagator::generate_StoreNameSloppy(int nameIndex)
.arg(m_state.accumulatorIn().descriptiveName(), type.descriptiveName()));
}
+ if (m_passManager != nullptr) {
+ m_passManager->analyzeWrite(m_function->qmlScope, name,
+ m_typeResolver->containedType(m_state.accumulatorIn()),
+ m_function->qmlScope, getCurrentSourceLocation());
+ }
+
m_state.setHasSideEffects(true);
addReadAccumulator(type);
}
@@ -797,6 +816,11 @@ void QQmlJSTypePropagator::propagatePropertyLookup(const QString &propertyName)
}
}
+ if (m_passManager != nullptr) {
+ m_passManager->analyzeRead(m_typeResolver->containedType(m_state.accumulatorIn()),
+ propertyName, m_function->qmlScope, getCurrentSourceLocation());
+ }
+
switch (m_state.accumulatorOut().variant()) {
case QQmlJSRegisterContent::ObjectEnum:
case QQmlJSRegisterContent::ExtensionObjectEnum:
@@ -863,6 +887,12 @@ void QQmlJSTypePropagator::generate_StoreProperty(int nameIndex, int base)
return;
}
+ if (m_passManager != nullptr) {
+ m_passManager->analyzeWrite(m_typeResolver->containedType(callBase), propertyName,
+ m_typeResolver->containedType(m_state.accumulatorIn()),
+ m_function->qmlScope, getCurrentSourceLocation());
+ }
+
m_state.setHasSideEffects(true);
addReadAccumulator(property);
addReadRegister(base, callBase);
@@ -982,6 +1012,12 @@ void QQmlJSTypePropagator::generate_CallProperty(int nameIndex, int base, int ar
checkDeprecated(m_typeResolver->containedType(callBase), propertyName, true);
+ if (m_passManager != nullptr) {
+ // TODO: Should there be an analyzeCall() in the future? (w. corresponding onCall in Pass)
+ m_passManager->analyzeRead(m_typeResolver->containedType(m_state.accumulatorIn()),
+ propertyName, m_function->qmlScope, getCurrentSourceLocation());
+ }
+
addReadRegister(base, callBase);
propagateCall(member.method(), argc, argv);
}
diff --git a/src/qmlcompiler/qqmljstypepropagator_p.h b/src/qmlcompiler/qqmljstypepropagator_p.h
index 323d07a595..ac6937da67 100644
--- a/src/qmlcompiler/qqmljstypepropagator_p.h
+++ b/src/qmlcompiler/qqmljstypepropagator_p.h
@@ -43,14 +43,18 @@
#include <private/qqmljsscope_p.h>
#include <private/qqmljscompilepass_p.h>
-
QT_BEGIN_NAMESPACE
+namespace QQmlSA {
+class PassManager;
+};
+
struct Q_QMLCOMPILER_PRIVATE_EXPORT QQmlJSTypePropagator : public QQmlJSCompilePass
{
QQmlJSTypePropagator(const QV4::Compiler::JSUnitGenerator *unitGenerator,
const QQmlJSTypeResolver *typeResolver, QQmlJSLogger *logger,
- QQmlJSTypeInfo *typeInfo = nullptr);
+ QQmlJSTypeInfo *typeInfo = nullptr,
+ QQmlSA::PassManager *passManager = nullptr);
InstructionAnnotations run(const Function *m_function, QQmlJS::DiagnosticMessage *error);
@@ -246,6 +250,7 @@ private:
QQmlJSRegisterContent m_returnType;
QQmlJSTypeInfo *m_typeInfo = nullptr;
+ QQmlSA::PassManager *m_passManager = nullptr;
// Not part of the state, as the back jumps are the reason for running multiple passes
QMultiHash<int, ExpectedRegisterState> m_jumpOriginRegisterStateByTargetInstructionOffset;
diff --git a/src/qmlcompiler/qqmlsa.cpp b/src/qmlcompiler/qqmlsa.cpp
index f86b3f0704..184ac79ee0 100644
--- a/src/qmlcompiler/qqmlsa.cpp
+++ b/src/qmlcompiler/qqmlsa.cpp
@@ -67,24 +67,6 @@ Element GenericPass::resolveType(QAnyStringView moduleName, QAnyStringView typeN
return module[typeName.toString()].scope;
}
-void SimplePropertyPass::run(const QQmlJSMetaProperty &property,
- const QList<QQmlJSMetaPropertyBinding> &bindings)
-{
- if (!bindings.isEmpty())
- run(property, bindings.constFirst());
- else
- run(property, {});
-}
-
-bool SimplePropertyPass::shouldRun(const Element &element, const QQmlJSMetaProperty &property,
- const QList<QQmlJSMetaPropertyBinding> &bindings)
-{
- if (!bindings.isEmpty())
- return shouldRun(element, property, bindings.constFirst());
- else
- return shouldRun(element, property, {});
-}
-
/*!
* \brief PassManager::registerElementPass registers ElementPass
with the pass manager.
@@ -95,9 +77,72 @@ void PassManager::registerElementPass(std::unique_ptr<ElementPass> pass)
m_elementPasses.push_back(std::move(pass));
}
-void PassManager::registerPropertyPass(std::unique_ptr<PropertyPass> pass)
+enum LookupMode { Register, Lookup };
+static QString lookupName(const QQmlSA::Element &element, LookupMode mode = Lookup)
+{
+ QString name;
+ if (element.isNull() || element->internalName().isEmpty()) {
+ // Bail out with an invalid name, this type is so screwed up we can't do anything reasonable
+ // with it We should have warned about it in another plac
+ if (element.isNull() || element->baseType().isNull())
+ return u"$INVALID$"_s;
+ name = element->baseType()->internalName();
+ } else {
+ name = element->internalName();
+ }
+
+ const QString filePath =
+ (mode == Register || !element->baseType() ? element : element->baseType())->filePath();
+
+ if (element->isComposite() && !filePath.endsWith(u".h"))
+ name += u'@' + filePath;
+ return name;
+}
+
+bool PassManager::registerPropertyPass(std::shared_ptr<PropertyPass> pass,
+ QAnyStringView moduleName, QAnyStringView typeName,
+ QAnyStringView propertyName)
+{
+ QString name;
+ if (!moduleName.isEmpty() && !typeName.isEmpty()) {
+ auto typeImporter = m_visitor->importer();
+ auto module = typeImporter->importModule(moduleName.toString());
+ auto element = module[typeName.toString()].scope;
+
+ if (element.isNull())
+ return false;
+
+ name = lookupName(element, Register);
+ }
+ m_propertyPasses.insert({ std::make_pair<>(name, propertyName.toString()), std::move(pass) });
+
+ return true;
+}
+
+void PassManager::addBindingSourceLocations(const Element &element, const Element &scope,
+ const QString prefix, bool isAttached)
{
- m_propertyPasses.push_back(std::move(pass));
+ const Element &currentScope = scope.isNull() ? element : scope;
+ const auto ownBindings = currentScope->ownPropertyBindings();
+ for (const auto &binding : ownBindings.values()) {
+ switch (binding.bindingType()) {
+ case QQmlJSMetaPropertyBinding::GroupProperty:
+ addBindingSourceLocations(element, binding.groupType(),
+ prefix + binding.propertyName() + u'.');
+ break;
+ case QQmlJSMetaPropertyBinding::AttachedProperty:
+ addBindingSourceLocations(element, binding.attachingType(),
+ prefix + binding.propertyName() + u'.', true);
+ break;
+ default:
+ m_bindingsByLocation.insert({ binding.sourceLocation().offset,
+ BindingInfo { prefix + binding.propertyName(), binding,
+ currentScope, isAttached } });
+
+ if (binding.bindingType() != QQmlJSMetaPropertyBinding::Script)
+ analyzeBinding(element, QQmlSA::Element(), binding.sourceLocation());
+ }
+ }
}
void PassManager::analyze(const Element &root)
@@ -106,16 +151,11 @@ void PassManager::analyze(const Element &root)
runStack.push_back(root);
while (!runStack.isEmpty()) {
auto element = runStack.takeLast();
+ addBindingSourceLocations(element);
for (auto &elementPass : m_elementPasses)
if (elementPass->shouldRun(element))
elementPass->run(element);
const auto ownPropertyBindings = element->ownPropertyBindings();
- for (auto it = ownPropertyBindings.keyBegin(); it != ownPropertyBindings.keyEnd(); ++it) {
- const auto bindings = element->propertyBindings(*it);
- for (auto &propertyPass : m_propertyPasses)
- if (propertyPass->shouldRun(element, element->property(*it), bindings))
- propertyPass->run(element->property(*it), bindings);
- }
for (auto it = element->childScopesBegin(); it != element->childScopesEnd(); ++it) {
if ((*it)->scopeType() == QQmlJSScope::QMLScope)
@@ -124,11 +164,67 @@ void PassManager::analyze(const Element &root)
}
}
+void PassManager::analyzeWrite(const Element &element, QString propertyName, const Element &value,
+ const Element &writeScope, QQmlJS::SourceLocation location)
+{
+ for (PropertyPass *pass : findPropertyUsePasses(element, propertyName))
+ pass->onWrite(element, propertyName, value, writeScope, location);
+}
+
+void PassManager::analyzeRead(const Element &element, QString propertyName,
+ const Element &readScope, QQmlJS::SourceLocation location)
+{
+ for (PropertyPass *pass : findPropertyUsePasses(element, propertyName))
+ pass->onRead(element, propertyName, readScope, location);
+}
+
+void PassManager::analyzeBinding(const Element &element, const QQmlSA::Element &value,
+ QQmlJS::SourceLocation location)
+{
+ const auto info = m_bindingsByLocation.find(location.offset);
+
+ // If there's no matching binding that means we're in a nested Ret somewhere inside an
+ // expression
+ if (info == m_bindingsByLocation.end())
+ return;
+
+ const QQmlSA::Element &bindingScope = info->second.bindingScope;
+ const QQmlJSMetaPropertyBinding &binding = info->second.binding;
+ const QString &propertyName = info->second.fullPropertyName;
+
+ for (PropertyPass *pass : findPropertyUsePasses(element, propertyName))
+ pass->onBinding(element, propertyName, binding, bindingScope, value);
+
+ if (!info->second.isAttached)
+ return;
+
+ for (PropertyPass *pass : findPropertyUsePasses(bindingScope->baseType(), propertyName))
+ pass->onBinding(element, propertyName, binding, bindingScope, value);
+}
+
bool PassManager::hasImportedModule(QAnyStringView module) const
{
return m_visitor->imports().contains(u"$module$." + module.toString());
}
+std::vector<PropertyPass *> PassManager::findPropertyUsePasses(const QQmlSA::Element &element,
+ const QString &propertyName)
+{
+ const QString typeName = lookupName(element);
+ std::vector<PropertyPass *> passes;
+ for (const auto &key :
+ { std::make_pair<>(typeName, propertyName), std::make_pair<>(QString(), propertyName),
+ std::make_pair<>(typeName, QString()) }) {
+ auto pass = m_propertyPasses.equal_range(key);
+ if (pass.first == pass.second)
+ continue;
+
+ for (auto it = pass.first; it != pass.second; it++)
+ passes.push_back(it->second.get());
+ }
+ return passes;
+}
+
void DebugElementPass::run(const Element &element) {
emitWarning(u"Type: " + element->baseTypeName());
if (auto bindings = element->propertyBindings(u"objectName"_s); !bindings.isEmpty()) {
@@ -140,23 +236,91 @@ void DebugElementPass::run(const Element &element) {
}
}
-void DebugPropertyPass::run(const QQmlJSMetaProperty &property,
- const QList<QQmlJSMetaPropertyBinding> &bindings)
+bool ElementPass::shouldRun(const Element &)
{
- emitWarning(u">> Property name: " + property.propertyName() + u"(binding count: "
- + QString::number(bindings.count()) + u')');
+ return true;
}
-bool PropertyPass::shouldRun(const Element &, const QQmlJSMetaProperty &, const QList<QQmlJSMetaPropertyBinding> &)
+PropertyPass::PropertyPass(PassManager *manager) : GenericPass(manager) { }
+
+void PropertyPass::onBinding(const Element &element, const QString &propertyName,
+ const QQmlJSMetaPropertyBinding &binding, const Element &bindingScope,
+ const Element &value)
{
- return true;
+ Q_UNUSED(element);
+ Q_UNUSED(propertyName);
+ Q_UNUSED(binding);
+ Q_UNUSED(bindingScope);
+ Q_UNUSED(value);
}
-bool ElementPass::shouldRun(const Element &)
+void PropertyPass::onRead(const Element &element, const QString &propertyName,
+ const Element &readScope, QQmlJS::SourceLocation location)
{
- return true;
+ Q_UNUSED(element);
+ Q_UNUSED(propertyName);
+ Q_UNUSED(readScope);
+ Q_UNUSED(location);
+}
+
+void PropertyPass::onWrite(const Element &element, const QString &propertyName,
+ const Element &value, const Element &writeScope,
+ QQmlJS::SourceLocation location)
+{
+ Q_UNUSED(element);
+ Q_UNUSED(propertyName);
+ Q_UNUSED(writeScope);
+ Q_UNUSED(value);
+ Q_UNUSED(location);
}
+DebugPropertyPass::DebugPropertyPass(QQmlSA::PassManager *manager) : QQmlSA::PropertyPass(manager)
+{
+}
+
+void DebugPropertyPass::onRead(const QQmlSA::Element &element, const QString &propertyName,
+ const QQmlSA::Element &readScope, QQmlJS::SourceLocation location)
+{
+ emitWarning(u"onRead "_s
+ + (element->internalName().isEmpty() ? element->baseTypeName()
+ : element->internalName())
+ + u' ' + propertyName + u' ' + readScope->internalName() + u' '
+ + QString::number(location.startLine) + u':'
+ + QString::number(location.startColumn),
+ location);
+}
+
+void DebugPropertyPass::onBinding(const QQmlSA::Element &element, const QString &propertyName,
+ const QQmlJSMetaPropertyBinding &binding,
+ const QQmlSA::Element &bindingScope, const QQmlSA::Element &value)
+{
+ const auto location = binding.sourceLocation();
+ emitWarning(u"onBinding element: '"_s
+ + (element->internalName().isEmpty() ? element->baseTypeName()
+ : element->internalName())
+ + u"' property: '"_s + propertyName + u"' value: '"_s
+ + (value.isNull()
+ ? u"NULL"_s
+ : (value->internalName().isNull() ? value->baseTypeName()
+ : value->internalName()))
+ + u"' binding_scope: '"_s
+ + (bindingScope->internalName().isEmpty() ? bindingScope->baseTypeName()
+ : bindingScope->internalName())
+ + u"' "_s + QString::number(location.startLine) + u':'
+ + QString::number(location.startColumn),
+ location);
+}
+
+void DebugPropertyPass::onWrite(const QQmlSA::Element &element, const QString &propertyName,
+ const QQmlSA::Element &value, const QQmlSA::Element &writeScope,
+ QQmlJS::SourceLocation location)
+{
+ emitWarning(u"onWrite "_s + element->baseTypeName() + u' ' + propertyName + u' '
+ + value->internalName() + u' ' + writeScope->internalName() + u' '
+ + QString::number(location.startLine) + u':'
+ + QString::number(location.startColumn),
+ location);
+}
}
QT_END_NAMESPACE
diff --git a/src/qmlcompiler/qqmlsa_p.h b/src/qmlcompiler/qqmlsa_p.h
index 9faa1fbcb9..5f2ebcd91a 100644
--- a/src/qmlcompiler/qqmlsa_p.h
+++ b/src/qmlcompiler/qqmlsa_p.h
@@ -43,12 +43,15 @@
#include <private/qqmljsscope_p.h>
+#include <map>
+#include <unordered_map>
#include <vector>
#include <memory>
QT_BEGIN_NAMESPACE
class QQmlJSTypeResolver;
+struct QQmlJSTypePropagator;
class QQmlJSImportVisitor;
namespace QQmlSA {
@@ -85,29 +88,16 @@ public:
class Q_QMLCOMPILER_EXPORT PropertyPass : public GenericPass
{
public:
- PropertyPass(PassManager *manager) : GenericPass(manager) { }
-
- virtual bool shouldRun(const Element &element, const QQmlJSMetaProperty &property,
- const QList<QQmlJSMetaPropertyBinding> &bindings);
- virtual void run(const QQmlJSMetaProperty &property,
- const QList<QQmlJSMetaPropertyBinding> &bindings) = 0;
-};
-
-class SimplePropertyPass : public PropertyPass
-{
-protected:
- SimplePropertyPass(PassManager *manager) : PropertyPass(manager) { }
-
- virtual void run(const QQmlJSMetaProperty &property,
- const QQmlJSMetaPropertyBinding &binding) = 0;
- virtual bool shouldRun(const Element &element, const QQmlJSMetaProperty &property,
- const QQmlJSMetaPropertyBinding &binding) = 0;
-
-private:
- void run(const QQmlJSMetaProperty &property,
- const QList<QQmlJSMetaPropertyBinding> &bindings) override;
- bool shouldRun(const Element &element, const QQmlJSMetaProperty &property,
- const QList<QQmlJSMetaPropertyBinding> &bindings) override;
+ PropertyPass(PassManager *manager);
+
+ virtual void onBinding(const QQmlSA::Element &element, const QString &propertyName,
+ const QQmlJSMetaPropertyBinding &binding,
+ const QQmlSA::Element &bindingScope, const QQmlSA::Element &value);
+ virtual void onRead(const QQmlSA::Element &element, const QString &propertyName,
+ const QQmlSA::Element &readScope, QQmlJS::SourceLocation location);
+ virtual void onWrite(const QQmlSA::Element &element, const QString &propertyName,
+ const QQmlSA::Element &value, const QQmlSA::Element &writeScope,
+ QQmlJS::SourceLocation location);
};
class Q_QMLCOMPILER_EXPORT LintPlugin
@@ -134,14 +124,42 @@ public:
Q_UNUSED(m_typeResolver);
}
void registerElementPass(std::unique_ptr<ElementPass> pass);
- void registerPropertyPass(std::unique_ptr<PropertyPass> pass);
+ bool registerPropertyPass(std::shared_ptr<PropertyPass> pass, QAnyStringView moduleName,
+ QAnyStringView typeName,
+ QAnyStringView propertyName = QAnyStringView());
void analyze(const Element &root);
bool hasImportedModule(QAnyStringView name) const;
private:
+ friend struct ::QQmlJSTypePropagator;
+
+ std::vector<PropertyPass *> findPropertyUsePasses(const QQmlSA::Element &element,
+ const QString &propertyName);
+
+ void analyzeWrite(const QQmlSA::Element &element, QString propertyName,
+ const QQmlSA::Element &value, const QQmlSA::Element &writeScope,
+ QQmlJS::SourceLocation location);
+ void analyzeRead(const QQmlSA::Element &element, QString propertyName,
+ const QQmlSA::Element &readScope, QQmlJS::SourceLocation location);
+ void analyzeBinding(const QQmlSA::Element &element, const QQmlSA::Element &value,
+ QQmlJS::SourceLocation location);
+
+ struct BindingInfo
+ {
+ QString fullPropertyName;
+ QQmlJSMetaPropertyBinding binding;
+ QQmlSA::Element bindingScope;
+ bool isAttached;
+ };
+
+ void addBindingSourceLocations(const QQmlSA::Element &element,
+ const QQmlSA::Element &scope = QQmlSA::Element(),
+ const QString prefix = QString(), bool isAttached = false);
+
std::vector<std::unique_ptr<ElementPass>> m_elementPasses;
- std::vector<std::unique_ptr<PropertyPass>> m_propertyPasses;
+ std::multimap<std::pair<QString, QString>, std::shared_ptr<PropertyPass>> m_propertyPasses;
+ std::unordered_map<quint32, BindingInfo> m_bindingsByLocation;
QQmlJSImportVisitor *m_visitor;
QQmlJSTypeResolver *m_typeResolver;
};
@@ -151,10 +169,19 @@ class Q_QMLCOMPILER_EXPORT DebugElementPass : public ElementPass
void run(const Element &element) override;
};
-class Q_QMLCOMPILER_EXPORT DebugPropertyPass : public PropertyPass
+class Q_QMLCOMPILER_EXPORT DebugPropertyPass : public QQmlSA::PropertyPass
{
- virtual void run(const QQmlJSMetaProperty &property,
- const QList<QQmlJSMetaPropertyBinding> &bindings) override;
+public:
+ DebugPropertyPass(QQmlSA::PassManager *manager);
+
+ void onRead(const QQmlSA::Element &element, const QString &propertyName,
+ const QQmlSA::Element &readScope, QQmlJS::SourceLocation location) override;
+ void onBinding(const QQmlSA::Element &element, const QString &propertyName,
+ const QQmlJSMetaPropertyBinding &binding, const QQmlSA::Element &bindingScope,
+ const QQmlSA::Element &value) override;
+ void onWrite(const QQmlSA::Element &element, const QString &propertyName,
+ const QQmlSA::Element &value, const QQmlSA::Element &writeScope,
+ QQmlJS::SourceLocation location) override;
};
}
diff --git a/tests/auto/qml/qmllint/data/pluginQuick_attached.qml b/tests/auto/qml/qmllint/data/pluginQuick_attached.qml
index 6474ff3ddb..fd2b2bc25f 100644
--- a/tests/auto/qml/qmllint/data/pluginQuick_attached.qml
+++ b/tests/auto/qml/qmllint/data/pluginQuick_attached.qml
@@ -1,6 +1,7 @@
import QtQuick
import QtQuick.Layouts
import QtQuick.Controls
+
QtObject {
// QtQuick
Accessible.name: "Foo"
@@ -9,11 +10,16 @@ QtObject {
// QtQuick.Layouts
Layout.minimumHeight: 3
+ property bool stackLayout: StackLayout.isCurrentItem // Read-only
// QtQuick.Templates
ScrollBar.vertical: ScrollBar {}
ScrollIndicator.vertical: ScrollIndicator {}
SplitView.fillWidth: true
StackView.visible: true
+ property int swipeView: SwipeView.index // Read-only
+ TextArea.flickable: TextArea {}
ToolTip.delay: 50
+ property bool tumbler: Tumbler.displacement // Read-only
+ property bool swipeDelegate: SwipeDelegate.pressed // Read-only
}
diff --git a/tests/auto/qml/qmllint/data/pluginQuick_attachedClean.qml b/tests/auto/qml/qmllint/data/pluginQuick_attachedClean.qml
new file mode 100644
index 0000000000..5114628428
--- /dev/null
+++ b/tests/auto/qml/qmllint/data/pluginQuick_attachedClean.qml
@@ -0,0 +1,31 @@
+import QtQuick
+import QtQuick.Layouts
+import QtQuick.Controls
+
+Item {
+ // QtQuick
+ Accessible.name: "Foo"
+ LayoutMirroring.enabled: true
+ EnterKey.type: Qt.EnterKeyGo
+
+ //QtQuick.Layouts
+ Layout.minimumHeight: 3
+ property bool stackLayout: StackLayout.isCurrentItem // Read-only
+
+ // QtQuick.Templates
+ SplitView.fillWidth: true
+ StackView.visible: true
+ property int swipeView: SwipeView.index // Read only, enforceable but complicated
+ Flickable {
+ TextArea.flickable: TextArea {}
+ ScrollIndicator.vertical: ScrollIndicator {}
+ ScrollBar.vertical: ScrollBar {}
+ }
+ ToolTip.delay: 50
+ TableView {
+ delegate: Item {
+ property bool tumbler: Tumbler.displacement // Read only, enforceable but complicated
+ }
+ }
+ property bool swipeDelegate: SwipeDelegate.pressed // Read only
+}
diff --git a/tests/auto/qml/qmllint/data/propertypass_pluginTest.qml b/tests/auto/qml/qmllint/data/propertypass_pluginTest.qml
index 14df718c22..728b014b85 100644
--- a/tests/auto/qml/qmllint/data/propertypass_pluginTest.qml
+++ b/tests/auto/qml/qmllint/data/propertypass_pluginTest.qml
@@ -1,8 +1,24 @@
-import QtQuick
+import QtQuick 2.0
Item {
- Image {
- x: 5
- Behavior on x { NumberAnimation { duration: 1000 } }
+ Text {
+ id: foo
+ text: "Foo" // Specific property we're targeting in one element
+ x: 5 // Property we're targeting regardless of type
+ y: x + 5 // Property used in a binding
+ font.bold: true // Not targeted
+ }
+ x: 5 // Property we're targeting regardless of type
+
+ ListModel { id: listModel }
+
+ ListView { // Entire type covered
+ model: listModel
+ height: 50
+ }
+
+ Component.onCompleted: {
+ console.log(foo.x); // Reading property from another component
+ foo.x = 30; // Writing property from another component
}
}
diff --git a/tests/auto/qml/qmllint/lintplugin.cpp b/tests/auto/qml/qmllint/lintplugin.cpp
index 91f3f41749..5a8a4f4a82 100644
--- a/tests/auto/qml/qmllint/lintplugin.cpp
+++ b/tests/auto/qml/qmllint/lintplugin.cpp
@@ -67,55 +67,42 @@ private:
class PropertyTest : public QQmlSA::PropertyPass
{
public:
- PropertyTest(QQmlSA::PassManager *manager) : QQmlSA::PropertyPass(manager)
+ PropertyTest(QQmlSA::PassManager *manager) : QQmlSA::PropertyPass(manager) { }
+
+ void onBinding(const QQmlSA::Element &element, const QString &propertyName,
+ const QQmlJSMetaPropertyBinding &binding, const QQmlSA::Element &bindingScope,
+ const QQmlSA::Element &value) override
{
- m_image = resolveType(u"QtQuick", u"Image");
+ emitWarning(u"Saw binding on %1 property %2 with value %3 (and type %4) in scope %5"_s
+ .arg(element->baseTypeName(), propertyName,
+ value.isNull()
+ ? u"NULL"_s
+ : (value->internalName().isNull() ? value->baseTypeName()
+ : value->baseTypeName()))
+ .arg(binding.bindingType())
+ .arg(bindingScope->baseTypeName()),
+ bindingScope->sourceLocation());
}
- bool shouldRun(const QQmlSA::Element &element, const QQmlJSMetaProperty &property,
- const QList<QQmlJSMetaPropertyBinding> &bindings) override
+ void onRead(const QQmlSA::Element &element, const QString &propertyName,
+ const QQmlSA::Element &readScope, QQmlJS::SourceLocation location) override
{
- return !bindings.isEmpty() && element->baseType() == m_image
- && property.propertyName() == u"x";
+ emitWarning(u"Saw read on %1 property %2 in scope %3"_s.arg(
+ element->baseTypeName(), propertyName, readScope->baseTypeName()),
+ location);
}
- void run(const QQmlJSMetaProperty &property,
- const QList<QQmlJSMetaPropertyBinding> &bindings) override
+ void onWrite(const QQmlSA::Element &element, const QString &propertyName,
+ const QQmlSA::Element &value, const QQmlSA::Element &writeScope,
+ QQmlJS::SourceLocation location) override
{
- if (property.typeName() != u"double") {
- emitWarning(u"Failed to verify x property");
- return;
- }
-
- bool foundInterceptor = false, foundValue = false;
- for (const QQmlJSMetaPropertyBinding &binding : bindings) {
- switch (binding.bindingType()) {
- case QQmlJSMetaPropertyBinding::Interceptor:
- foundInterceptor = true;
- break;
- case QQmlJSMetaPropertyBinding::NumberLiteral:
- foundValue = true;
- if (binding.numberValue() != 5) {
- emitWarning(u"Binding has wrong value");
- return;
- }
- break;
- default:
- emitWarning(u"Found unexpected binding on x property");
- return;
- }
- }
-
- if (!foundInterceptor || !foundValue) {
- emitWarning(u"Didn't see all expected bindings");
- return;
- }
-
- emitWarning(u"PropertyTest OK");
+ emitWarning(u"Saw write on %1 property %2 with value %3 in scope %4"_s.arg(
+ element->baseTypeName(), propertyName,
+ (value->internalName().isNull() ? value->baseTypeName()
+ : value->internalName()),
+ writeScope->baseTypeName()),
+ location);
}
-
-private:
- QQmlSA::Element m_image;
};
class HasImportedModuleTest : public QQmlSA::ElementPass
@@ -148,7 +135,10 @@ void LintPlugin::registerPasses(QQmlSA::PassManager *manager, const QQmlSA::Elem
return;
manager->registerElementPass(std::make_unique<ElementTest>(manager));
- manager->registerPropertyPass(std::make_unique<PropertyTest>(manager));
+ manager->registerPropertyPass(std::make_unique<PropertyTest>(manager), "QtQuick", "Text",
+ "text");
+ manager->registerPropertyPass(std::make_unique<PropertyTest>(manager), "", "", "x");
+ manager->registerPropertyPass(std::make_unique<PropertyTest>(manager), "QtQuick", "ListView");
if (manager->hasImportedModule("QtQuick.Controls")) {
if (manager->hasImportedModule("QtQuick")) {
if (manager->hasImportedModule("QtQuick.Window")) {
diff --git a/tests/auto/qml/qmllint/tst_qmllint.cpp b/tests/auto/qml/qmllint/tst_qmllint.cpp
index 8bc293646f..2fe0bb8446 100644
--- a/tests/auto/qml/qmllint/tst_qmllint.cpp
+++ b/tests/auto/qml/qmllint/tst_qmllint.cpp
@@ -1652,7 +1652,25 @@ void TestQmllint::testPlugin()
QVERIFY(pluginFound);
runTest("elementpass_pluginTest.qml", Result { { Message { u"ElementTest OK"_s, 4, 5 } } });
- runTest("propertypass_pluginTest.qml", Result { { Message { u"OK"_s } } });
+ runTest("propertypass_pluginTest.qml",
+ Result {
+ { // Specific binding for specific property
+ Message {
+ u"Saw binding on Text property text with value NULL (and type 3) in scope Text"_s },
+
+ // Property on any type
+ Message { u"Saw read on Text property x in scope Text"_s },
+ Message {
+ u"Saw binding on Text property x with value NULL (and type 2) in scope Text"_s },
+ Message { u"Saw read on Text property x in scope Item"_s },
+ Message { u"Saw write on Text property x with value int in scope Item"_s },
+ Message {
+ u"Saw binding on Item property x with value NULL (and type 2) in scope Item"_s },
+ // ListModel
+ Message {
+ u"Saw binding on ListView property model with value ListModel (and type 8) in scope ListView"_s },
+ Message {
+ u"Saw binding on ListView property height with value NULL (and type 2) in scope ListView"_s } } });
runTest("controlsWithQuick_pluginTest.qml",
Result { { Message { u"QtQuick.Controls, QtQuick and QtQuick.Window present"_s } } });
runTest("controlsWithoutQuick_pluginTest.qml",
@@ -1719,26 +1737,36 @@ void TestQmllint::quickPlugin()
Message {
u"LayoutDirection attached property only works with Items and Windows"_s },
Message { u"Layout must be attached to Item elements"_s },
- Message { u"StackView attached property only works with Items"_s } } });
+ Message { u"StackView attached property only works with Items"_s },
+ Message { u"TextArea must be attached to a Flickable"_s },
+ Message { u"StackLayout must be attached to an Item"_s },
+ Message {
+ u"Tumbler: attached properties of Tumbler must be accessed through a delegate item"_s },
+ Message {
+ u"Attached properties of SwipeDelegate must be accessed through an Item"_s },
+ Message { u"SwipeView must be attached to an Item"_s } } });
+
runTest("pluginQuick_swipeDelegate.qml",
Result { {
- Message {
- u"SwipeDelegate: Cannot use horizontal anchors with contentItem; unable to layout the item."_s,
- 6, 43 },
- Message {
- u"SwipeDelegate: Cannot use horizontal anchors with background; unable to layout the item."_s,
- 7, 43 },
- Message { u"SwipeDelegate: Cannot set both behind and left/right properties"_s,
- 9, 9 },
- Message {
- u"SwipeDelegate: Cannot use horizontal anchors with contentItem; unable to layout the item."_s,
- 13, 47 },
- Message {
- u"SwipeDelegate: Cannot use horizontal anchors with background; unable to layout the item."_s,
- 14, 42 },
- Message { u"SwipeDelegate: Cannot set both behind and left/right properties"_s,
- 16, 9 },
- } });
+ Message {
+ u"SwipeDelegate: Cannot use horizontal anchors with contentItem; unable to layout the item."_s,
+ 6, 43 },
+ Message {
+ u"SwipeDelegate: Cannot use horizontal anchors with background; unable to layout the item."_s,
+ 7, 43 },
+ Message { u"SwipeDelegate: Cannot set both behind and left/right properties"_s,
+ 9, 9 },
+ Message {
+ u"SwipeDelegate: Cannot use horizontal anchors with contentItem; unable to layout the item."_s,
+ 13, 47 },
+ Message {
+ u"SwipeDelegate: Cannot use horizontal anchors with background; unable to layout the item."_s,
+ 14, 42 },
+ Message { u"SwipeDelegate: Cannot set both behind and left/right properties"_s,
+ 16, 9 },
+ } });
+
+ runTest("pluginQuick_attachedClean.qml", Result::clean());
}
#endif