aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-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