1// Copyright (C) 2022 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
3
4#include "quicklintplugin.h"
5
6QT_BEGIN_NAMESPACE
7
8using namespace Qt::StringLiterals;
9
10static constexpr QQmlSA::LoggerWarningId quickLayoutPositioning { "Quick.layout-positioning" };
11static constexpr QQmlSA::LoggerWarningId quickAttachedPropertyType { "Quick.attached-property-type" };
12static constexpr QQmlSA::LoggerWarningId quickControlsNativeCustomize { "Quick.controls-native-customize" };
13static constexpr QQmlSA::LoggerWarningId quickAnchorCombinations { "Quick.anchor-combinations" };
14static constexpr QQmlSA::LoggerWarningId quickUnexpectedVarType { "Quick.unexpected-var-type" };
15static constexpr QQmlSA::LoggerWarningId quickPropertyChangesParsed { "Quick.property-changes-parsed" };
16static constexpr QQmlSA::LoggerWarningId quickControlsAttachedPropertyReuse { "Quick.controls-attached-property-reuse" };
17static constexpr QQmlSA::LoggerWarningId quickAttachedPropertyReuse { "Quick.attached-property-reuse" };
18
19ForbiddenChildrenPropertyValidatorPass::ForbiddenChildrenPropertyValidatorPass(
20 QQmlSA::PassManager *manager)
21 : QQmlSA::ElementPass(manager)
22{
23}
24
25void ForbiddenChildrenPropertyValidatorPass::addWarning(QAnyStringView moduleName,
26 QAnyStringView typeName,
27 QAnyStringView propertyName,
28 QAnyStringView warning)
29{
30 auto element = resolveType(moduleName, typeName);
31 if (!element.isNull())
32 m_types[element].append(t: { .propertyName: propertyName.toString(), .message: warning.toString() });
33}
34
35bool ForbiddenChildrenPropertyValidatorPass::shouldRun(const QQmlSA::Element &element)
36{
37 if (!element.parentScope())
38 return false;
39
40 for (const auto &pair : std::as_const(t&: m_types).asKeyValueRange()) {
41 if (element.parentScope().inherits(pair.first))
42 return true;
43 }
44
45 return false;
46}
47
48void ForbiddenChildrenPropertyValidatorPass::run(const QQmlSA::Element &element)
49{
50 for (const auto &elementPair : std::as_const(t&: m_types).asKeyValueRange()) {
51 const QQmlSA::Element &type = elementPair.first;
52 if (!element.parentScope().inherits(type))
53 continue;
54
55 for (const auto &warning : elementPair.second) {
56 if (!element.hasOwnPropertyBindings(propertyName: warning.propertyName))
57 continue;
58
59 const auto bindings = element.ownPropertyBindings(propertyName: warning.propertyName);
60 const auto firstBinding = bindings.constBegin().value();
61 emitWarning(diagnostic: warning.message, id: quickLayoutPositioning, srcLocation: firstBinding.sourceLocation());
62 }
63 break;
64 }
65}
66
67AttachedPropertyTypeValidatorPass::AttachedPropertyTypeValidatorPass(QQmlSA::PassManager *manager)
68 : QQmlSA::PropertyPass(manager)
69{
70}
71
72QString AttachedPropertyTypeValidatorPass::addWarning(TypeDescription attachType,
73 QList<TypeDescription> allowedTypes,
74 bool allowInDelegate, QAnyStringView warning)
75{
76 QVarLengthArray<QQmlSA::Element, 4> elements;
77
78 const QQmlSA::Element attachedType = resolveAttached(moduleName: attachType.module, typeName: attachType.name);
79 if (!attachedType) {
80 emitWarning(
81 diagnostic: "Cannot find attached type for %1/%2"_L1.arg(args&: attachType.module, args&: attachType.name),
82 id: quickAttachedPropertyType);
83 return QString();
84 }
85
86 for (const TypeDescription &desc : allowedTypes) {
87 const QQmlSA::Element type = resolveType(moduleName: desc.module, typeName: desc.name);
88 if (type.isNull())
89 continue;
90 elements.push_back(t: type);
91 }
92
93 m_attachedTypes.insert(
94 hash: { std::make_pair<>(x: attachedType.internalId(),
95 y: Warning{ .allowedTypes: elements, .allowInDelegate: allowInDelegate, .message: warning.toString() }) });
96
97 return attachedType.internalId();
98}
99
100void AttachedPropertyTypeValidatorPass::checkWarnings(const QQmlSA::Element &element,
101 const QQmlSA::Element &scopeUsedIn,
102 const QQmlSA::SourceLocation &location)
103{
104 auto warning = m_attachedTypes.constFind(key: element.internalId());
105 if (warning == m_attachedTypes.cend())
106 return;
107 for (const QQmlSA::Element &type : warning->allowedTypes) {
108 if (scopeUsedIn.inherits(type))
109 return;
110 }
111
112 if (warning->allowInDelegate) {
113 if (scopeUsedIn.isPropertyRequired(propertyName: u"index"_s)
114 || scopeUsedIn.isPropertyRequired(propertyName: u"model"_s))
115 return;
116
117 // If the scope is at the root level, we cannot know whether it will be used
118 // as a delegate or not.
119 // ### TODO: add a method to check whether a scope is the global scope
120 // so that we do not need to use internalId
121 if (!scopeUsedIn.parentScope() || scopeUsedIn.parentScope().internalId() == u"global"_s)
122 return;
123
124 for (const QQmlSA::Binding &binding :
125 scopeUsedIn.parentScope().propertyBindings(propertyName: u"delegate"_s)) {
126 if (!binding.hasObject())
127 continue;
128 if (binding.objectType() == scopeUsedIn)
129 return;
130 }
131 }
132
133 emitWarning(diagnostic: warning->message, id: quickAttachedPropertyType, srcLocation: location);
134}
135
136void AttachedPropertyTypeValidatorPass::onBinding(const QQmlSA::Element &element,
137 const QString &propertyName,
138 const QQmlSA::Binding &binding,
139 const QQmlSA::Element &bindingScope,
140 const QQmlSA::Element &value)
141{
142 Q_UNUSED(value)
143
144 // We can only analyze simple attached bindings since we don't see
145 // the grouped and attached properties that lead up to this here.
146 //
147 // TODO: This is very crude.
148 // We should add API for grouped and attached properties.
149 if (propertyName.count(c: QLatin1Char('.')) > 1)
150 return;
151
152 checkWarnings(element: bindingScope.baseType(), scopeUsedIn: element, location: binding.sourceLocation());
153}
154
155void AttachedPropertyTypeValidatorPass::onRead(const QQmlSA::Element &element,
156 const QString &propertyName,
157 const QQmlSA::Element &readScope,
158 QQmlSA::SourceLocation location)
159{
160 // If the attachment does not have such a property or method then
161 // it's either a more general error or an enum. Enums are fine.
162 if (element.hasProperty(propertyName) || element.hasMethod(methodName: propertyName))
163 checkWarnings(element, scopeUsedIn: readScope, location);
164}
165
166void AttachedPropertyTypeValidatorPass::onWrite(const QQmlSA::Element &element,
167 const QString &propertyName,
168 const QQmlSA::Element &value,
169 const QQmlSA::Element &writeScope,
170 QQmlSA::SourceLocation location)
171{
172 Q_UNUSED(propertyName)
173 Q_UNUSED(value)
174
175 checkWarnings(element, scopeUsedIn: writeScope, location);
176}
177
178ControlsNativeValidatorPass::ControlsNativeValidatorPass(QQmlSA::PassManager *manager)
179 : QQmlSA::ElementPass(manager)
180{
181 m_elements = {
182 ControlElement { .name: "Control",
183 .restrictedProperties: QStringList { "background", "contentItem", "leftPadding", "rightPadding",
184 "topPadding", "bottomPadding", "horizontalPadding",
185 "verticalPadding", "padding" },
186 .isInModuleControls: false, .isControl: true },
187 ControlElement { .name: "Button", .restrictedProperties: QStringList { "indicator" } },
188 ControlElement {
189 .name: "ApplicationWindow",
190 .restrictedProperties: QStringList { "background", "contentItem", "header", "footer", "menuBar" } },
191 ControlElement { .name: "ComboBox", .restrictedProperties: QStringList { "indicator" } },
192 ControlElement { .name: "Dial", .restrictedProperties: QStringList { "handle" } },
193 ControlElement { .name: "GroupBox", .restrictedProperties: QStringList { "label" } },
194 ControlElement { .name: "$internal$.QQuickIndicatorButton", .restrictedProperties: QStringList { "indicator" }, .isInModuleControls: false },
195 ControlElement { .name: "Label", .restrictedProperties: QStringList { "background" } },
196 ControlElement { .name: "MenuItem", .restrictedProperties: QStringList { "arrow" } },
197 ControlElement { .name: "Page", .restrictedProperties: QStringList { "header", "footer" } },
198 ControlElement { .name: "Popup", .restrictedProperties: QStringList { "background", "contentItem" } },
199 ControlElement { .name: "RangeSlider", .restrictedProperties: QStringList { "handle" } },
200 ControlElement { .name: "Slider", .restrictedProperties: QStringList { "handle" } },
201 ControlElement { .name: "$internal$.QQuickSwipe",
202 .restrictedProperties: QStringList { "leftItem", "behindItem", "rightItem" }, .isInModuleControls: false },
203 ControlElement { .name: "TextArea", .restrictedProperties: QStringList { "background" } },
204 ControlElement { .name: "TextField", .restrictedProperties: QStringList { "background" } },
205 };
206
207 for (const QString &module : { u"QtQuick.Controls.macOS"_s, u"QtQuick.Controls.Windows"_s }) {
208 if (!manager->hasImportedModule(name: module))
209 continue;
210
211 QQmlSA::Element control = resolveType(moduleName: module, typeName: "Control");
212
213 for (ControlElement &element : m_elements) {
214 auto type = resolveType(moduleName: element.isInModuleControls ? module : "QtQuick.Templates",
215 typeName: element.name);
216
217 if (type.isNull())
218 continue;
219
220 element.inheritsControl = !element.isControl && type.inherits(control);
221 element.element = type;
222 }
223
224 m_elements.removeIf(pred: [](const ControlElement &element) { return element.element.isNull(); });
225
226 break;
227 }
228}
229
230bool ControlsNativeValidatorPass::shouldRun(const QQmlSA::Element &element)
231{
232 for (const ControlElement &controlElement : m_elements) {
233 // If our element inherits control, we don't have to individually check for them here.
234 if (controlElement.inheritsControl)
235 continue;
236 if (element.inherits(controlElement.element))
237 return true;
238 }
239 return false;
240}
241
242void ControlsNativeValidatorPass::run(const QQmlSA::Element &element)
243{
244 for (const ControlElement &controlElement : m_elements) {
245 if (element.inherits(controlElement.element)) {
246 for (const QString &propertyName : controlElement.restrictedProperties) {
247 if (element.hasOwnPropertyBindings(propertyName)) {
248 emitWarning(QStringLiteral("Not allowed to override \"%1\" because native "
249 "styles cannot be customized: See "
250 "https://doc-snapshots.qt.io/qt6-dev/"
251 "qtquickcontrols-customize.html#customization-"
252 "reference for more information.")
253 .arg(a: propertyName),
254 id: quickControlsNativeCustomize, srcLocation: element.sourceLocation());
255 }
256 }
257 // Since all the different types we have rules for don't inherit from each other (except
258 // for Control) we don't have to keep checking whether other types match once we've
259 // found one that has been inherited from.
260 if (!controlElement.isControl)
261 break;
262 }
263 }
264}
265
266AnchorsValidatorPass::AnchorsValidatorPass(QQmlSA::PassManager *manager)
267 : QQmlSA::ElementPass(manager)
268 , m_item(resolveType(moduleName: "QtQuick", typeName: "Item"))
269{
270}
271
272bool AnchorsValidatorPass::shouldRun(const QQmlSA::Element &element)
273{
274 return !m_item.isNull() && element.inherits(m_item)
275 && element.hasOwnPropertyBindings(propertyName: u"anchors"_s);
276}
277
278void AnchorsValidatorPass::run(const QQmlSA::Element &element)
279{
280 enum BindingLocation { Exists = 1, Own = (1 << 1) };
281 QHash<QString, qint8> bindings;
282
283 const QStringList properties = { u"left"_s, u"right"_s, u"horizontalCenter"_s,
284 u"top"_s, u"bottom"_s, u"verticalCenter"_s,
285 u"baseline"_s };
286
287 QList<QQmlSA::Binding> anchorBindings = element.propertyBindings(propertyName: u"anchors"_s);
288
289 for (qsizetype i = anchorBindings.size() - 1; i >= 0; i--) {
290 auto groupType = anchorBindings[i].groupType();
291 if (groupType.isNull())
292 continue;
293
294 for (const QString &name : properties) {
295
296 const auto &propertyBindings = groupType.ownPropertyBindings(propertyName: name);
297 if (propertyBindings.begin() == propertyBindings.end())
298 continue;
299
300 bool isUndefined = false;
301 for (const auto &propertyBinding : propertyBindings) {
302 if (propertyBinding.hasUndefinedScriptValue()) {
303 isUndefined = true;
304 break;
305 }
306 }
307
308 if (isUndefined)
309 bindings[name] = 0;
310 else
311 bindings[name] |= Exists | ((i == 0) ? Own : 0);
312 }
313 }
314
315 auto ownSourceLocation = [&](QStringList properties) -> QQmlSA::SourceLocation {
316 QQmlSA::SourceLocation warnLoc;
317
318 for (const QString &name : properties) {
319 if (bindings[name] & Own) {
320 QQmlSA::Element groupType = QQmlSA::Element{ anchorBindings[0].groupType() };
321 auto bindings = groupType.ownPropertyBindings(propertyName: name);
322 Q_ASSERT(bindings.begin() != bindings.end());
323 warnLoc = bindings.begin().value().sourceLocation();
324 break;
325 }
326 }
327 return warnLoc;
328 };
329
330 if ((bindings[u"left"_s] & bindings[u"right"_s] & bindings[u"horizontalCenter"_s]) & Exists) {
331 QQmlSA::SourceLocation warnLoc =
332 ownSourceLocation({ u"left"_s, u"right"_s, u"horizontalCenter"_s });
333
334 if (warnLoc.isValid()) {
335 emitWarning(
336 diagnostic: "Cannot specify left, right, and horizontalCenter anchors at the same time.",
337 id: quickAnchorCombinations, srcLocation: warnLoc);
338 }
339 }
340
341 if ((bindings[u"top"_s] & bindings[u"bottom"_s] & bindings[u"verticalCenter"_s]) & Exists) {
342 QQmlSA::SourceLocation warnLoc =
343 ownSourceLocation({ u"top"_s, u"bottom"_s, u"verticalCenter"_s });
344 if (warnLoc.isValid()) {
345 emitWarning(diagnostic: "Cannot specify top, bottom, and verticalCenter anchors at the same time.",
346 id: quickAnchorCombinations, srcLocation: warnLoc);
347 }
348 }
349
350 if ((bindings[u"baseline"_s] & (bindings[u"bottom"_s] | bindings[u"verticalCenter"_s]))
351 & Exists) {
352 QQmlSA::SourceLocation warnLoc =
353 ownSourceLocation({ u"baseline"_s, u"bottom"_s, u"verticalCenter"_s });
354 if (warnLoc.isValid()) {
355 emitWarning(diagnostic: "Baseline anchor cannot be used in conjunction with top, bottom, or "
356 "verticalCenter anchors.",
357 id: quickAnchorCombinations, srcLocation: warnLoc);
358 }
359 }
360}
361
362ControlsSwipeDelegateValidatorPass::ControlsSwipeDelegateValidatorPass(QQmlSA::PassManager *manager)
363 : QQmlSA::ElementPass(manager)
364 , m_swipeDelegate(resolveType(moduleName: "QtQuick.Controls", typeName: "SwipeDelegate"))
365{
366}
367
368bool ControlsSwipeDelegateValidatorPass::shouldRun(const QQmlSA::Element &element)
369{
370 return !m_swipeDelegate.isNull() && element.inherits(m_swipeDelegate);
371}
372
373void ControlsSwipeDelegateValidatorPass::run(const QQmlSA::Element &element)
374{
375 for (const auto &property : { u"background"_s, u"contentItem"_s }) {
376 for (const auto &binding : element.ownPropertyBindings(propertyName: property)) {
377 if (!binding.hasObject())
378 continue;
379 const QQmlSA::Element element = QQmlSA::Element{ binding.objectType() };
380 const auto &bindings = element.propertyBindings(propertyName: u"anchors"_s);
381 if (bindings.isEmpty())
382 continue;
383
384 if (bindings.first().bindingType() != QQmlSA::BindingType::GroupProperty)
385 continue;
386
387 auto anchors = bindings.first().groupType();
388 for (const auto &disallowed : { u"fill"_s, u"centerIn"_s, u"left"_s, u"right"_s }) {
389 if (anchors.hasPropertyBindings(name: disallowed)) {
390 QQmlSA::SourceLocation location;
391 const auto &ownBindings = anchors.ownPropertyBindings(propertyName: disallowed);
392 if (ownBindings.begin() != ownBindings.end()) {
393 location = ownBindings.begin().value().sourceLocation();
394 }
395
396 emitWarning(
397 diagnostic: u"SwipeDelegate: Cannot use horizontal anchors with %1; unable to layout the item."_s
398 .arg(a: property),
399 id: quickAnchorCombinations, srcLocation: location);
400 break;
401 }
402 }
403 break;
404 }
405 }
406
407 const auto &swipe = element.ownPropertyBindings(propertyName: u"swipe"_s);
408 if (swipe.begin() == swipe.end())
409 return;
410
411 const auto firstSwipe = swipe.begin().value();
412 if (firstSwipe.bindingType() != QQmlSA::BindingType::GroupProperty)
413 return;
414
415 auto group = firstSwipe.groupType();
416
417 const std::array ownDirBindings = { group.ownPropertyBindings(propertyName: u"right"_s),
418 group.ownPropertyBindings(propertyName: u"left"_s),
419 group.ownPropertyBindings(propertyName: u"behind"_s) };
420
421 auto ownBindingIterator =
422 std::find_if(first: ownDirBindings.begin(), last: ownDirBindings.end(),
423 pred: [](const auto &bindings) { return bindings.begin() != bindings.end(); });
424
425 if (ownBindingIterator == ownDirBindings.end())
426 return;
427
428 if (group.hasPropertyBindings(name: u"behind"_s)
429 && (group.hasPropertyBindings(name: u"right"_s) || group.hasPropertyBindings(name: u"left"_s))) {
430 emitWarning(diagnostic: "SwipeDelegate: Cannot set both behind and left/right properties",
431 id: quickAnchorCombinations, srcLocation: ownBindingIterator->begin().value().sourceLocation());
432 }
433}
434
435VarBindingTypeValidatorPass::VarBindingTypeValidatorPass(
436 QQmlSA::PassManager *manager,
437 const QMultiHash<QString, TypeDescription> &expectedPropertyTypes)
438 : QQmlSA::PropertyPass(manager)
439{
440 QMultiHash<QString, QQmlSA::Element> propertyTypes;
441
442 for (const auto &pair : expectedPropertyTypes.asKeyValueRange()) {
443 const QQmlSA::Element propType = pair.second.module.isEmpty()
444 ? resolveBuiltinType(typeName: pair.second.name)
445 : resolveType(moduleName: pair.second.module, typeName: pair.second.name);
446 if (!propType.isNull())
447 propertyTypes.insert(key: pair.first, value: propType);
448 }
449
450 m_expectedPropertyTypes = propertyTypes;
451}
452
453void VarBindingTypeValidatorPass::onBinding(const QQmlSA::Element &element,
454 const QString &propertyName,
455 const QQmlSA::Binding &binding,
456 const QQmlSA::Element &bindingScope,
457 const QQmlSA::Element &value)
458{
459 Q_UNUSED(element);
460 Q_UNUSED(bindingScope);
461
462 const auto range = m_expectedPropertyTypes.equal_range(key: propertyName);
463
464 if (range.first == range.second)
465 return;
466
467 QQmlSA::Element bindingType;
468
469 if (!value.isNull()) {
470 bindingType = value;
471 } else {
472 if (QQmlSA::Binding::isLiteralBinding(binding.bindingType())) {
473 bindingType = resolveLiteralType(binding);
474 } else {
475 switch (binding.bindingType()) {
476 case QQmlSA::BindingType::Object:
477 bindingType = QQmlSA::Element{ binding.objectType() };
478 break;
479 case QQmlSA::BindingType::Script:
480 break;
481 default:
482 return;
483 }
484 }
485 }
486
487 if (std::find_if(first: range.first, last: range.second,
488 pred: [&](const QQmlSA::Element &scope) { return bindingType.inherits(scope); })
489 == range.second) {
490
491 const bool bindingTypeIsComposite = bindingType.isComposite();
492 if (bindingTypeIsComposite && !bindingType.baseType()) {
493 /* broken module or missing import, there is nothing we
494 can really check here, as something is amiss. We
495 simply skip this binding, and assume that whatever
496 caused the breakage here will already cause another
497 warning somewhere else.
498 */
499 return;
500 }
501 const QString bindingTypeName =
502 bindingTypeIsComposite ? bindingType.baseType().name()
503 : bindingType.name();
504 QStringList expectedTypeNames;
505
506 for (auto it = range.first; it != range.second; it++)
507 expectedTypeNames << it.value().name();
508
509 emitWarning(diagnostic: u"Unexpected type for property \"%1\" expected %2 got %3"_s.arg(
510 args: propertyName, args: expectedTypeNames.join(sep: u", "_s), args: bindingTypeName),
511 id: quickUnexpectedVarType, srcLocation: binding.sourceLocation());
512 }
513}
514
515void AttachedPropertyReuse::onRead(const QQmlSA::Element &element, const QString &propertyName,
516 const QQmlSA::Element &readScope,
517 QQmlSA::SourceLocation location)
518{
519 const auto range = usedAttachedTypes.equal_range(key: readScope);
520 const auto attachedTypeAndLocation = std::find_if(
521 first: range.first, last: range.second, pred: [&](const ElementAndLocation &elementAndLocation) {
522 return elementAndLocation.element == element;
523 });
524 if (attachedTypeAndLocation != range.second) {
525 const QQmlSA::SourceLocation attachedLocation = attachedTypeAndLocation->location;
526
527 // Ignore enum accesses, as these will not cause the attached object to be created.
528 // Also ignore anything we cannot determine.
529 if (!element.hasProperty(propertyName) && !element.hasMethod(methodName: propertyName))
530 return;
531
532 for (QQmlSA::Element scope = readScope.parentScope(); !scope.isNull();
533 scope = scope.parentScope()) {
534 const auto range = usedAttachedTypes.equal_range(key: scope);
535 bool found = false;
536 for (auto it = range.first; it != range.second; ++it) {
537 if (it->element == element) {
538 found = true;
539 break;
540 }
541 }
542 if (!found)
543 continue;
544
545 const QString id = resolveElementToId(element: scope, context: readScope);
546 const QQmlSA::SourceLocation idInsertLocation{ attachedLocation.offset(), 0,
547 attachedLocation.startLine(),
548 attachedLocation.startColumn() };
549 QQmlSA::FixSuggestion suggestion{ "Reference it by id instead:"_L1, idInsertLocation,
550 id.isEmpty() ? u"<id>."_s : (id + '.'_L1) };
551
552 if (id.isEmpty())
553 suggestion.setHint("You first have to give the element an id"_L1);
554 else
555 suggestion.setAutoApplicable();
556
557 emitWarning(diagnostic: "Using attached type %1 already initialized in a parent scope."_L1.arg(
558 args: element.name()),
559 id: category, srcLocation: attachedLocation, fix: suggestion);
560 return;
561 }
562
563 return;
564 }
565
566 if (element.hasProperty(propertyName))
567 return; // an actual property
568
569 QQmlSA::Element type = resolveTypeInFileScope(typeName: propertyName);
570 QQmlSA::Element attached = resolveAttachedInFileScope(typeName: propertyName);
571 if (!type || !attached)
572 return;
573
574 if (category == quickControlsAttachedPropertyReuse) {
575 for (QQmlSA::Element parent = attached; parent; parent = parent.baseType()) {
576 // ### TODO: Make it possible to resolve QQuickAttachedPropertyPropagator
577 // so that we don't have to compare the internal id
578 if (parent.internalId() == "QQuickAttachedPropertyPropagator"_L1) {
579 usedAttachedTypes.insert(key: readScope, value: {.element: attached, .location: location});
580 break;
581 }
582 }
583
584 } else {
585 usedAttachedTypes.insert(key: readScope, value: {.element: attached, .location: location});
586 }
587}
588
589void AttachedPropertyReuse::onWrite(const QQmlSA::Element &element, const QString &propertyName,
590 const QQmlSA::Element &value, const QQmlSA::Element &writeScope,
591 QQmlSA::SourceLocation location)
592{
593 Q_UNUSED(value);
594 onRead(element, propertyName, readScope: writeScope, location);
595}
596
597void QmlLintQuickPlugin::registerPasses(QQmlSA::PassManager *manager,
598 const QQmlSA::Element &rootElement)
599{
600 const QQmlSA::LoggerWarningId attachedReuseCategory = [manager]() {
601 if (manager->isCategoryEnabled(category: quickAttachedPropertyReuse))
602 return quickAttachedPropertyReuse;
603 if (manager->isCategoryEnabled(category: qmlAttachedPropertyReuse))
604 return qmlAttachedPropertyReuse;
605 return quickControlsAttachedPropertyReuse;
606 }();
607
608 const bool hasQuick = manager->hasImportedModule(name: "QtQuick");
609 const bool hasQuickLayouts = manager->hasImportedModule(name: "QtQuick.Layouts");
610 const bool hasQuickControls = manager->hasImportedModule(name: "QtQuick.Templates")
611 || manager->hasImportedModule(name: "QtQuick.Controls")
612 || manager->hasImportedModule(name: "QtQuick.Controls.Basic");
613
614 Q_UNUSED(rootElement);
615
616 if (hasQuick) {
617 manager->registerElementPass(pass: std::make_unique<AnchorsValidatorPass>(args&: manager));
618 manager->registerElementPass(pass: std::make_unique<PropertyChangesValidatorPass>(args&: manager));
619
620 auto forbiddenChildProperty =
621 std::make_unique<ForbiddenChildrenPropertyValidatorPass>(args&: manager);
622
623 for (const QString &element : { u"Grid"_s, u"Flow"_s }) {
624 for (const QString &property : { u"anchors"_s, u"x"_s, u"y"_s }) {
625 forbiddenChildProperty->addWarning(
626 moduleName: "QtQuick", typeName: element, propertyName: property,
627 warning: u"Cannot specify %1 for items inside %2. %2 will not function."_s.arg(
628 args: property, args: element));
629 }
630 }
631
632 if (hasQuickLayouts) {
633 forbiddenChildProperty->addWarning(
634 moduleName: "QtQuick.Layouts", typeName: "Layout", propertyName: "anchors",
635 warning: "Detected anchors on an item that is managed by a layout. This is undefined "
636 u"behavior; use Layout.alignment instead.");
637 forbiddenChildProperty->addWarning(
638 moduleName: "QtQuick.Layouts", typeName: "Layout", propertyName: "x",
639 warning: "Detected x on an item that is managed by a layout. This is undefined "
640 u"behavior; use Layout.leftMargin or Layout.rightMargin instead.");
641 forbiddenChildProperty->addWarning(
642 moduleName: "QtQuick.Layouts", typeName: "Layout", propertyName: "y",
643 warning: "Detected y on an item that is managed by a layout. This is undefined "
644 u"behavior; use Layout.topMargin or Layout.bottomMargin instead.");
645 forbiddenChildProperty->addWarning(
646 moduleName: "QtQuick.Layouts", typeName: "Layout", propertyName: "width",
647 warning: "Detected width on an item that is managed by a layout. This is undefined "
648 u"behavior; use implicitWidth or Layout.preferredWidth instead.");
649 forbiddenChildProperty->addWarning(
650 moduleName: "QtQuick.Layouts", typeName: "Layout", propertyName: "height",
651 warning: "Detected height on an item that is managed by a layout. This is undefined "
652 u"behavior; use implictHeight or Layout.preferredHeight instead.");
653 }
654
655 manager->registerElementPass(pass: std::move(forbiddenChildProperty));
656 }
657
658 auto attachedPropertyType = std::make_shared<AttachedPropertyTypeValidatorPass>(args&: manager);
659
660 auto addAttachedWarning = [&](TypeDescription attachedType, QList<TypeDescription> allowedTypes,
661 QAnyStringView warning, bool allowInDelegate = false) {
662 QString attachedTypeName = attachedPropertyType->addWarning(attachType: attachedType, allowedTypes,
663 allowInDelegate, warning);
664 if (attachedTypeName.isEmpty())
665 return;
666
667 manager->registerPropertyPass(pass: attachedPropertyType, moduleName: attachedType.module,
668 typeName: u"$internal$."_s + attachedTypeName, propertyName: {}, allowInheritance: false);
669 };
670
671 auto addVarBindingWarning =
672 [&](QAnyStringView moduleName, QAnyStringView typeName,
673 const QMultiHash<QString, TypeDescription> &expectedPropertyTypes) {
674 auto varBindingType = std::make_shared<VarBindingTypeValidatorPass>(
675 args&: manager, args: expectedPropertyTypes);
676 for (const auto &propertyName : expectedPropertyTypes.uniqueKeys()) {
677 manager->registerPropertyPass(pass: varBindingType, moduleName, typeName,
678 propertyName);
679 }
680 };
681
682 if (hasQuick) {
683 addVarBindingWarning("QtQuick", "TableView",
684 { { "columnWidthProvider", { .module: "", .name: "function" } },
685 { "rowHeightProvider", { .module: "", .name: "function" } } });
686 addAttachedWarning({ .module: "QtQuick", .name: "Accessible" }, { { .module: "QtQuick", .name: "Item" } },
687 "Accessible must be attached to an Item or an Action");
688 addAttachedWarning({ .module: "QtQuick", .name: "LayoutMirroring" },
689 { { .module: "QtQuick", .name: "Item" }, { .module: "QtQuick", .name: "Window" } },
690 "LayoutMirroring attached property only works with Items and Windows");
691 addAttachedWarning({ .module: "QtQuick", .name: "EnterKey" }, { { .module: "QtQuick", .name: "Item" } },
692 "EnterKey attached property only works with Items");
693 }
694 if (hasQuickLayouts) {
695 addAttachedWarning({ .module: "QtQuick.Layouts", .name: "Layout" }, { { .module: "QtQuick", .name: "Item" } },
696 "Layout must be attached to Item elements");
697 addAttachedWarning({ .module: "QtQuick.Layouts", .name: "StackLayout" }, { { .module: "QtQuick", .name: "Item" } },
698 "StackLayout must be attached to an Item");
699 }
700
701
702 if (hasQuickControls) {
703 manager->registerElementPass(pass: std::make_unique<ControlsSwipeDelegateValidatorPass>(args&: manager));
704 manager->registerPropertyPass(pass: std::make_unique<AttachedPropertyReuse>(
705 args&: manager, args: attachedReuseCategory), moduleName: "", typeName: "");
706
707 addAttachedWarning({ .module: "QtQuick.Templates", .name: "ScrollBar" },
708 { { .module: "QtQuick", .name: "Flickable" }, { .module: "QtQuick.Templates", .name: "ScrollView" } },
709 "ScrollBar must be attached to a Flickable or ScrollView");
710 addAttachedWarning({ .module: "QtQuick.Templates", .name: "ScrollIndicator" },
711 { { .module: "QtQuick", .name: "Flickable" } },
712 "ScrollIndicator must be attached to a Flickable");
713 addAttachedWarning({ .module: "QtQuick.Templates", .name: "TextArea" }, { { .module: "QtQuick", .name: "Flickable" } },
714 "TextArea must be attached to a Flickable");
715 addAttachedWarning({ .module: "QtQuick.Templates", .name: "SplitView" }, { { .module: "QtQuick", .name: "Item" } },
716 "SplitView attached property only works with Items");
717 addAttachedWarning({ .module: "QtQuick.Templates", .name: "StackView" }, { { .module: "QtQuick", .name: "Item" } },
718 "StackView attached property only works with Items");
719 addAttachedWarning({ .module: "QtQuick.Templates", .name: "ToolTip" }, { { .module: "QtQuick", .name: "Item" } },
720 "ToolTip must be attached to an Item");
721 addAttachedWarning({ .module: "QtQuick.Templates", .name: "SwipeDelegate" }, { { .module: "QtQuick", .name: "Item" } },
722 "Attached properties of SwipeDelegate must be accessed through an Item");
723 addAttachedWarning({ .module: "QtQuick.Templates", .name: "SwipeView" }, { { .module: "QtQuick", .name: "Item" } },
724 "SwipeView must be attached to an Item");
725 addVarBindingWarning("QtQuick.Templates", "Tumbler",
726 { { "contentItem", { .module: "QtQuick", .name: "PathView" } },
727 { "contentItem", { .module: "QtQuick", .name: "ListView" } } });
728 addVarBindingWarning("QtQuick.Templates", "SpinBox",
729 { { "textFromValue", { .module: "", .name: "function" } },
730 { "valueFromText", { .module: "", .name: "function" } } });
731 } else if (attachedReuseCategory != quickControlsAttachedPropertyReuse) {
732 manager->registerPropertyPass(pass: std::make_unique<AttachedPropertyReuse>(
733 args&: manager, args: attachedReuseCategory), moduleName: "", typeName: "");
734 }
735
736 if (manager->hasImportedModule(name: u"QtQuick.Controls.macOS"_s)
737 || manager->hasImportedModule(name: u"QtQuick.Controls.Windows"_s))
738 manager->registerElementPass(pass: std::make_unique<ControlsNativeValidatorPass>(args&: manager));
739}
740
741PropertyChangesValidatorPass::PropertyChangesValidatorPass(QQmlSA::PassManager *manager)
742 : QQmlSA::ElementPass(manager)
743 , m_propertyChanges(resolveType(moduleName: "QtQuick", typeName: "PropertyChanges"))
744{
745}
746
747bool PropertyChangesValidatorPass::shouldRun(const QQmlSA::Element &element)
748{
749 return !m_propertyChanges.isNull() && element.inherits(m_propertyChanges);
750}
751
752void PropertyChangesValidatorPass::run(const QQmlSA::Element &element)
753{
754 const QQmlSA::Binding::Bindings bindings = element.ownPropertyBindings();
755
756 const auto target =
757 std::find_if(first: bindings.constBegin(), last: bindings.constEnd(),
758 pred: [](const auto binding) { return binding.propertyName() == u"target"_s; });
759 if (target == bindings.constEnd())
760 return;
761
762 QString targetId = u"<id>"_s;
763 const auto targetLocation = target.value().sourceLocation();
764 const QString targetBinding = sourceCode(location: targetLocation);
765 const QQmlSA::Element targetElement = resolveIdToElement(id: targetBinding, context: element);
766 if (!targetElement.isNull())
767 targetId = targetBinding;
768
769 bool hadCustomParsedBindings = false;
770 for (auto it = bindings.constBegin(); it != bindings.constEnd(); ++it) {
771 const auto &propertyName = it.key();
772 const auto &propertyBinding = it.value();
773 if (element.hasProperty(propertyName))
774 continue;
775
776 const QQmlSA::SourceLocation bindingLocation = propertyBinding.sourceLocation();
777 if (!targetElement.isNull() && !targetElement.hasProperty(propertyName)) {
778 emitWarning(
779 diagnostic: "Unknown property \"%1\" in PropertyChanges."_L1.arg(args: propertyName),
780 id: quickPropertyChangesParsed, srcLocation: bindingLocation);
781 continue;
782 }
783
784 QString binding = sourceCode(location: bindingLocation);
785 if (binding.length() > 16)
786 binding = binding.left(n: 13) + "..."_L1;
787
788 hadCustomParsedBindings = true;
789 emitWarning(diagnostic: "Property \"%1\" is custom-parsed in PropertyChanges. "
790 "You should phrase this binding as \"%2.%1: %3\""_L1.arg(args: propertyName, args&: targetId,
791 args&: binding),
792 id: quickPropertyChangesParsed, srcLocation: bindingLocation);
793 }
794
795 if (hadCustomParsedBindings && !targetElement.isNull()) {
796 emitWarning(diagnostic: "You should remove any bindings on the \"target\" property and avoid "
797 "custom-parsed bindings in PropertyChanges.",
798 id: quickPropertyChangesParsed, srcLocation: targetLocation);
799 }
800}
801
802QT_END_NAMESPACE
803
804#include "moc_quicklintplugin.cpp"
805

Provided by KDAB

Privacy Policy
Start learning QML with our Intro Training
Find out more

source code of qtdeclarative/src/plugins/qmllint/quick/quicklintplugin.cpp