qmllint/Quick: Warn about customizing native QtQuick.Controls styles

Certain properties in native QtQuick.Controls styles may not be
overridden. This patch adds a pass to the Quick qmllint plugin to warn
when a user modifies them.

The pass also uses the fact that these types (except Control) don't
inherit from each other to skip over checking for additional types
once a matching type is found.

Fixes: QTBUG-96737
Task-number: QTBUG-102277
Change-Id: I02f696aae716de45014736651385b6607eed6d15
Reviewed-by: Mitch Curtis <mitch.curtis@qt.io>
Reviewed-by: Fabian Kosmale <fabian.kosmale@qt.io>
Reviewed-by: Qt CI Bot <qt_ci_bot@qt-project.org>
This commit is contained in:
Maximilian Goldstein 2022-04-12 15:59:13 +02:00
parent fc3f4e5834
commit 894b8a605e
2 changed files with 120 additions and 2 deletions

View File

@ -49,9 +49,103 @@ void LayoutChildrenValidatorPass::run(const QQmlSA::Element &element)
element->sourceLocation());
}
ControlsNativeValidatorPass::ControlsNativeValidatorPass(QQmlSA::PassManager *manager)
: QQmlSA::ElementPass(manager)
{
m_elements = {
ControlElement { "Control",
QStringList { "background", "contentItem", "leftPadding", "rightPadding",
"topPadding", "bottomPadding", "horizontalPadding",
"verticalPadding", "padding" },
false, true },
ControlElement { "Button", QStringList { "indicator" } },
ControlElement {
"ApplicationWindow",
QStringList { "background", "contentItem", "header", "footer", "menuBar" } },
ControlElement { "ComboBox", QStringList { "indicator" } },
ControlElement { "Dial", QStringList { "handle" } },
ControlElement { "Dialog", QStringList { "header", "footer" } },
ControlElement { "GroupBox", QStringList { "label" } },
ControlElement { "$internal$.QQuickIndicatorButton", QStringList { "indicator" }, false },
ControlElement { "Label", QStringList { "background" } },
ControlElement { "MenuItem", QStringList { "arrow" } },
ControlElement { "Page", QStringList { "header", "footer" } },
ControlElement { "Popup", QStringList { "background", "contentItem" } },
ControlElement { "RangeSlider", QStringList { "handle" } },
ControlElement { "Slider", QStringList { "handle" } },
ControlElement { "$internal$.QQuickSwipe",
QStringList { "leftItem", "behindItem", "rightItem" }, false },
ControlElement { "TextArea", QStringList { "background" } },
ControlElement { "TextField", QStringList { "background" } },
};
for (const QString &module : { u"QtQuick.Controls.macOS"_qs, u"QtQuick.Controls.Windows"_qs }) {
if (!manager->hasImportedModule(module))
continue;
QQmlSA::Element control = resolveType(module, "Control");
for (ControlElement &element : m_elements) {
auto type = resolveType(element.isInModuleControls ? module : "QtQuick.Templates",
element.name);
if (type.isNull())
continue;
element.inheritsControl = !element.isControl && type->inherits(control);
element.element = type;
}
m_elements.removeIf([](const ControlElement &element) { return element.element.isNull(); });
break;
}
}
bool ControlsNativeValidatorPass::shouldRun(const QQmlSA::Element &element)
{
for (const ControlElement &controlElement : m_elements) {
// If our element inherits control, we don't have to individually check for them here.
if (controlElement.inheritsControl)
continue;
if (element->inherits(controlElement.element))
return true;
}
return false;
}
void ControlsNativeValidatorPass::run(const QQmlSA::Element &element)
{
for (const ControlElement &controlElement : m_elements) {
if (element->inherits(controlElement.element)) {
for (const QString &propertyName : controlElement.restrictedProperties) {
if (element->hasOwnPropertyBindings(propertyName)) {
emitWarning(QStringLiteral("Not allowed to override \"%1\" because native "
"styles cannot be customized: See "
"https://doc-snapshots.qt.io/qt6-dev/"
"qtquickcontrols2-customize.html#customization-"
"reference for more information.")
.arg(propertyName),
element->sourceLocation());
}
}
// Since all the different types we have rules for don't inherit from each other (except
// for Control) we don't have to keep checking whether other types match once we've
// found one that has been inherited from.
if (!controlElement.isControl)
break;
}
}
}
void QmlLintQuickPlugin::registerPasses(QQmlSA::PassManager *manager,
const QQmlSA::Element &rootElement)
{
Q_UNUSED(rootElement);
manager->registerElementPass(std::make_unique<LayoutChildrenValidatorPass>(manager));
if (manager->hasImportedModule(u"QtQuick.Layouts"_qs))
manager->registerElementPass(std::make_unique<LayoutChildrenValidatorPass>(manager));
if (manager->hasImportedModule(u"QtQuick.Controls.macOS"_qs)
|| manager->hasImportedModule(u"QtQuick.Controls.Windows"_qs))
manager->registerElementPass(std::make_unique<ControlsNativeValidatorPass>(manager));
}

View File

@ -29,7 +29,9 @@
#ifndef QUICKLINTPLUGIN_H
#define QUICKLINTPLUGIN_H
#include <QtPlugin>
#include <QtCore/qplugin.h>
#include <QtCore/qlist.h>
#include <QtQmlCompiler/private/qqmlsa_p.h>
class QmlLintQuickPlugin : public QObject, public QQmlSA::LintPlugin
@ -54,4 +56,26 @@ private:
QQmlSA::Element m_layout;
};
class ControlsNativeValidatorPass : public QQmlSA::ElementPass
{
public:
ControlsNativeValidatorPass(QQmlSA::PassManager *manager);
bool shouldRun(const QQmlSA::Element &element) override;
void run(const QQmlSA::Element &element) override;
private:
struct ControlElement
{
QString name;
QStringList restrictedProperties;
bool isInModuleControls = true;
bool isControl = false;
bool inheritsControl = false;
QQmlSA::Element element = {};
};
QList<ControlElement> m_elements;
};
#endif // QUICKLINTPLUGIN_H