diff --git a/src/plugins/qmllint/quick/quicklintplugin.cpp b/src/plugins/qmllint/quick/quicklintplugin.cpp index 7ec12f7369..d021479846 100644 --- a/src/plugins/qmllint/quick/quicklintplugin.cpp +++ b/src/plugins/qmllint/quick/quicklintplugin.cpp @@ -317,6 +317,79 @@ void AnchorsValidatorPass::run(const QQmlSA::Element &element) } } +ControlsSwipeDelegateValidatorPass::ControlsSwipeDelegateValidatorPass(QQmlSA::PassManager *manager) + : QQmlSA::ElementPass(manager) +{ + m_swipeDelegate = resolveType("QtQuick.Controls", "SwipeDelegate"); +} + +bool ControlsSwipeDelegateValidatorPass::shouldRun(const QQmlSA::Element &element) +{ + return !m_swipeDelegate.isNull() && element->inherits(m_swipeDelegate); +} + +void ControlsSwipeDelegateValidatorPass::run(const QQmlSA::Element &element) +{ + for (const auto &property : { u"background"_s, u"contentItem"_s }) { + auto bindings = element->ownPropertyBindings(property); + for (auto it = bindings.first; it != bindings.second; it++) { + if (!it->hasObject()) + continue; + const QQmlSA::Element element = it->objectType(); + const auto bindings = element->propertyBindings(u"anchors"_s); + if (bindings.isEmpty()) + continue; + + if (bindings.first().bindingType() != QQmlJSMetaPropertyBinding::GroupProperty) + continue; + + auto anchors = bindings.first().groupType(); + for (const auto &disallowed : { u"fill"_s, u"centerIn"_s, u"left"_s, u"right"_s }) { + if (anchors->hasPropertyBindings(disallowed)) { + QQmlJS::SourceLocation location; + auto ownBindings = anchors->ownPropertyBindings(disallowed); + if (ownBindings.first != ownBindings.second) { + location = ownBindings.first->sourceLocation(); + } + + emitWarning( + u"SwipeDelegate: Cannot use horizontal anchors with %1; unable to layout the item."_s + .arg(property), + location); + break; + } + } + break; + } + } + + auto swipe = element->ownPropertyBindings(u"swipe"_s); + if (swipe.first == swipe.second) + return; + + if (swipe.first->bindingType() != QQmlJSMetaPropertyBinding::GroupProperty) + return; + + auto group = swipe.first->groupType(); + + const auto ownDirBindings = { group->ownPropertyBindings(u"right"_s), + group->ownPropertyBindings(u"left"_s), + group->ownPropertyBindings(u"behind"_s) }; + + auto ownBindingIterator = + std::find_if(ownDirBindings.begin(), ownDirBindings.end(), + [](const auto &pair) { return pair.first != pair.second; }); + + if (ownBindingIterator == ownDirBindings.end()) + return; + + if (group->hasPropertyBindings(u"behind"_s) + && (group->hasPropertyBindings(u"right"_s) || group->hasPropertyBindings(u"left"_s))) { + emitWarning("SwipeDelegate: Cannot set both behind and left/right properties", + ownBindingIterator->first->sourceLocation()); + } +} + void QmlLintQuickPlugin::registerPasses(QQmlSA::PassManager *manager, const QQmlSA::Element &rootElement) { @@ -397,6 +470,8 @@ void QmlLintQuickPlugin::registerPasses(QQmlSA::PassManager *manager, "StackView attached property only works with Items"); attachedPropertyType->addWarning("ToolTip", { { "QtQuick", "Item" } }, "ToolTip must be attached to an Item"); + + manager->registerElementPass(std::make_unique(manager)); } manager->registerElementPass(std::move(attachedPropertyType)); diff --git a/src/plugins/qmllint/quick/quicklintplugin.h b/src/plugins/qmllint/quick/quicklintplugin.h index 71dbdb3f4f..64e6c94a9a 100644 --- a/src/plugins/qmllint/quick/quicklintplugin.h +++ b/src/plugins/qmllint/quick/quicklintplugin.h @@ -128,6 +128,18 @@ private: QQmlSA::Element m_item; }; +class ControlsSwipeDelegateValidatorPass : public QQmlSA::ElementPass +{ +public: + ControlsSwipeDelegateValidatorPass(QQmlSA::PassManager *manager); + + bool shouldRun(const QQmlSA::Element &element) override; + void run(const QQmlSA::Element &element) override; + +private: + QQmlSA::Element m_swipeDelegate; +}; + QT_END_NAMESPACE #endif // QUICKLINTPLUGIN_H diff --git a/tests/auto/qml/qmllint/data/pluginQuick_swipeDelegate.qml b/tests/auto/qml/qmllint/data/pluginQuick_swipeDelegate.qml new file mode 100644 index 0000000000..a6c3b2c743 --- /dev/null +++ b/tests/auto/qml/qmllint/data/pluginQuick_swipeDelegate.qml @@ -0,0 +1,19 @@ +import QtQuick.Controls +import QtQuick + +Item { + SwipeDelegate { + contentItem: Item { anchors.left: parent.left } + background: Item { anchors.right: parent.right } + + swipe.left: Item {} + swipe.behind: Item {} + } + SwipeDelegate { + contentItem: Item { anchors.centerIn: parent } + background: Item { anchors.fill: parent } + + swipe.right: Item {} + swipe.behind: Item {} + } +} diff --git a/tests/auto/qml/qmllint/tst_qmllint.cpp b/tests/auto/qml/qmllint/tst_qmllint.cpp index 72fabba059..8bc293646f 100644 --- a/tests/auto/qml/qmllint/tst_qmllint.cpp +++ b/tests/auto/qml/qmllint/tst_qmllint.cpp @@ -1514,23 +1514,28 @@ void TestQmllint::searchWarnings(const QJsonArray &warnings, const QString &subs } const auto toDescription = [](const QJsonArray &warnings, const QString &substring, - bool must = true) { + quint32 line, quint32 column, bool must = true) { // Note: this actually produces a very poorly formatted multi-line // description, but this is how we also do it in cleanQmlCode test case, // so this should suffice. in any case this mainly aids the debugging // and CI stays (or should stay) clean. - return QStringLiteral("qmllint output '%1' %2 contain '%3'") - .arg(QString::fromUtf8(QJsonDocument(warnings).toJson(QJsonDocument::Compact)), - must ? u"must" : u"must NOT", substring); + QString msg = QStringLiteral("qmllint output '%1' %2 contain '%3'") + .arg(QString::fromUtf8( + QJsonDocument(warnings).toJson(QJsonDocument::Compact)), + must ? u"must" : u"must NOT", substring); + if (line != 0 || column != 0) + msg += u" (%1:%2)"_s.arg(line).arg(column); + + return msg; }; if (shouldContain == StringContained) { if (!contains) - qWarning() << toDescription(warnings, substring); + qWarning() << toDescription(warnings, substring, line, column); QVERIFY(contains); } else { if (contains) - qWarning() << toDescription(warnings, substring, false); + qWarning() << toDescription(warnings, substring, line, column, false); QVERIFY(!contains); } } @@ -1715,6 +1720,25 @@ void TestQmllint::quickPlugin() 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 } } }); + 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 }, + } }); } #endif