diff options
| -rw-r--r-- | src/plugins/qmllint/quick/quicklintplugin.cpp | 178 | ||||
| -rw-r--r-- | src/plugins/qmllint/quick/quicklintplugin.h | 20 | ||||
| -rw-r--r-- | src/qmlcompiler/qqmljscompilepass_p.h | 1 | ||||
| -rw-r--r-- | src/qmlcompiler/qqmljsfunctioninitializer.cpp | 7 | ||||
| -rw-r--r-- | src/qmlcompiler/qqmljslinter.cpp | 11 | ||||
| -rw-r--r-- | src/qmlcompiler/qqmljslintercodegen.cpp | 3 | ||||
| -rw-r--r-- | src/qmlcompiler/qqmljslintercodegen_p.h | 9 | ||||
| -rw-r--r-- | src/qmlcompiler/qqmljstypepropagator.cpp | 42 | ||||
| -rw-r--r-- | src/qmlcompiler/qqmljstypepropagator_p.h | 9 | ||||
| -rw-r--r-- | src/qmlcompiler/qqmlsa.cpp | 232 | ||||
| -rw-r--r-- | src/qmlcompiler/qqmlsa_p.h | 83 | ||||
| -rw-r--r-- | tests/auto/qml/qmllint/data/pluginQuick_attached.qml | 6 | ||||
| -rw-r--r-- | tests/auto/qml/qmllint/data/pluginQuick_attachedClean.qml | 31 | ||||
| -rw-r--r-- | tests/auto/qml/qmllint/data/propertypass_pluginTest.qml | 24 | ||||
| -rw-r--r-- | tests/auto/qml/qmllint/lintplugin.cpp | 74 | ||||
| -rw-r--r-- | tests/auto/qml/qmllint/tst_qmllint.cpp | 66 |
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 ¤tScope = 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 |
