diff options
author | Maximilian Goldstein <max.goldstein@qt.io> | 2022-04-26 12:50:08 +0200 |
---|---|---|
committer | Maximilian Goldstein <max.goldstein@qt.io> | 2022-04-27 15:29:24 +0200 |
commit | 3265013769b566039436358ae7762916ffa88628 (patch) | |
tree | ec318358aafc7d7db42c0c9d468ad6cfd24b53f7 | |
parent | 4b3e99b9a28b0641265a4afed720e5359e35ffda (diff) |
QmlLintQuickPlugin: Warn about attached types used in wrong elements
The engine will warn when various attached types are used in elements
where they are not supported.
This patch replicates this behavior in qmllint with the exception that
we cannot handle them being used in bindings right now.
Task-number: QTBUG-102277
Task-number: QTBUG-102859
Change-Id: Ic41c9338d8625c5185dbd658cc8987f3c00f18c3
Reviewed-by: Qt CI Bot <qt_ci_bot@qt-project.org>
Reviewed-by: Fabian Kosmale <fabian.kosmale@qt.io>
Reviewed-by: Andrei Golubev <andrei.golubev@qt.io>
-rw-r--r-- | src/plugins/qmllint/quick/quicklintplugin.cpp | 96 | ||||
-rw-r--r-- | src/plugins/qmllint/quick/quicklintplugin.h | 26 | ||||
-rw-r--r-- | tests/auto/qml/qmllint/data/pluginQuick_attached.qml | 19 | ||||
-rw-r--r-- | tests/auto/qml/qmllint/tst_qmllint.cpp | 12 |
4 files changed, 151 insertions, 2 deletions
diff --git a/src/plugins/qmllint/quick/quicklintplugin.cpp b/src/plugins/qmllint/quick/quicklintplugin.cpp index 9d9e4e1350..b2248d3896 100644 --- a/src/plugins/qmllint/quick/quicklintplugin.cpp +++ b/src/plugins/qmllint/quick/quicklintplugin.cpp @@ -76,6 +76,60 @@ void ForbiddenChildrenPropertyValidatorPass::run(const QQmlSA::Element &element) } } +AttachedPropertyTypeValidatorPass::AttachedPropertyTypeValidatorPass(QQmlSA::PassManager *manager) + : QQmlSA::ElementPass(manager) +{ +} + +void AttachedPropertyTypeValidatorPass::addWarning( + QAnyStringView attachedTypeName, + QList<AttachedPropertyTypeValidatorPass::TypeDescription> allowedTypes, + QAnyStringView warning) +{ + AttachedPropertyTypeValidatorPass::Warning warningInfo; + warningInfo.message = warning.toString(); + + for (const TypeDescription &description : allowedTypes) { + auto type = resolveType(description.module, description.name); + + if (type.isNull()) + continue; + + warningInfo.allowedTypes.push_back(type); + } + + m_attachedTypes[attachedTypeName.toString()] = warningInfo; +} + +bool AttachedPropertyTypeValidatorPass::shouldRun(const QQmlSA::Element &element) +{ + for (const auto &pair : m_attachedTypes.asKeyValueRange()) { + if (element->hasOwnPropertyBindings(pair.first)) + return true; + } + + 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()); + } + } + } +} + ControlsNativeValidatorPass::ControlsNativeValidatorPass(QQmlSA::PassManager *manager) : QQmlSA::ElementPass(manager) { @@ -230,9 +284,14 @@ void AnchorsValidatorPass::run(const QQmlSA::Element &element) void QmlLintQuickPlugin::registerPasses(QQmlSA::PassManager *manager, const QQmlSA::Element &rootElement) { + const bool hasQuick = manager->hasImportedModule("QtQuick"); + const bool hasQuickLayouts = manager->hasImportedModule("QtQuick.Layouts"); + const bool hasQuickControls = manager->hasImportedModule("QtQuick.Templates") + || manager->hasImportedModule("QtQuick.Controls"); + Q_UNUSED(rootElement); - if (manager->hasImportedModule("QtQuick")) { + if (hasQuick) { manager->registerElementPass(std::make_unique<AnchorsValidatorPass>(manager)); auto forbiddenChildProperty = @@ -247,7 +306,7 @@ void QmlLintQuickPlugin::registerPasses(QQmlSA::PassManager *manager, } } - if (manager->hasImportedModule("QtQuick.Layouts")) { + if (hasQuickLayouts) { forbiddenChildProperty->addWarning( "QtQuick.Layouts", "Layout", "anchors", "Detected anchors on an item that is managed by a layout. This is undefined " @@ -273,6 +332,39 @@ void QmlLintQuickPlugin::registerPasses(QQmlSA::PassManager *manager, manager->registerElementPass(std::move(forbiddenChildProperty)); } + auto attachedPropertyType = std::make_unique<AttachedPropertyTypeValidatorPass>(manager); + + 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"); + } + + if (hasQuickLayouts) { + attachedPropertyType->addWarning("Layout", { { "QtQuick", "Item" } }, + "Layout must be attached to Item elements"); + } + + 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::move(attachedPropertyType)); + if (manager->hasImportedModule(u"QtQuick.Controls.macOS"_qs) || manager->hasImportedModule(u"QtQuick.Controls.Windows"_qs)) manager->registerElementPass(std::make_unique<ControlsNativeValidatorPass>(manager)); diff --git a/src/plugins/qmllint/quick/quicklintplugin.h b/src/plugins/qmllint/quick/quicklintplugin.h index 322c3a0332..a1c5218e7a 100644 --- a/src/plugins/qmllint/quick/quicklintplugin.h +++ b/src/plugins/qmllint/quick/quicklintplugin.h @@ -66,6 +66,32 @@ private: QHash<QQmlSA::Element, QVarLengthArray<Warning, 8>> m_types; }; +class AttachedPropertyTypeValidatorPass : public QQmlSA::ElementPass +{ +public: + struct TypeDescription + { + QString module; + QString name; + }; + + AttachedPropertyTypeValidatorPass(QQmlSA::PassManager *manager); + + void addWarning(QAnyStringView attachedTypeName, QList<TypeDescription> allowedTypes, + QAnyStringView warning); + + bool shouldRun(const QQmlSA::Element &element) override; + void run(const QQmlSA::Element &element) override; + +private: + struct Warning + { + QVarLengthArray<QQmlSA::Element, 4> allowedTypes; + QString message; + }; + QHash<QString, Warning> m_attachedTypes; +}; + class ControlsNativeValidatorPass : public QQmlSA::ElementPass { public: diff --git a/tests/auto/qml/qmllint/data/pluginQuick_attached.qml b/tests/auto/qml/qmllint/data/pluginQuick_attached.qml new file mode 100644 index 0000000000..6474ff3ddb --- /dev/null +++ b/tests/auto/qml/qmllint/data/pluginQuick_attached.qml @@ -0,0 +1,19 @@ +import QtQuick +import QtQuick.Layouts +import QtQuick.Controls +QtObject { + // QtQuick + Accessible.name: "Foo" + LayoutMirroring.enabled: true + EnterKey.type: Qt.EnterKeyGo + + // QtQuick.Layouts + Layout.minimumHeight: 3 + + // QtQuick.Templates + ScrollBar.vertical: ScrollBar {} + ScrollIndicator.vertical: ScrollIndicator {} + SplitView.fillWidth: true + StackView.visible: true + ToolTip.delay: 50 +} diff --git a/tests/auto/qml/qmllint/tst_qmllint.cpp b/tests/auto/qml/qmllint/tst_qmllint.cpp index 82f4deb0b3..7844e63d27 100644 --- a/tests/auto/qml/qmllint/tst_qmllint.cpp +++ b/tests/auto/qml/qmllint/tst_qmllint.cpp @@ -1696,6 +1696,18 @@ void TestQmllint::quickPlugin() u"Cannot specify x for items inside Flow. Flow will not function."_qs }, Message { u"Cannot specify y for items inside Flow. Flow will not function."_qs } } }); + runTest("pluginQuick_attached.qml", + Result { + { Message { u"ToolTip must be attached to an Item"_qs }, + Message { u"SplitView attached property only works with Items"_qs }, + Message { u"ScrollIndicator must be attached to a Flickable"_qs }, + Message { u"ScrollBar must be attached to a Flickable or ScrollView"_qs }, + Message { u"Accessible must be attached to an Item"_qs }, + Message { u"EnterKey attached property only works with Items"_qs }, + Message { + u"LayoutDirection attached property only works with Items and Windows"_qs }, + Message { u"Layout must be attached to Item elements"_qs }, + Message { u"StackView attached property only works with Items"_qs } } }); } #endif |