Qt
Internal/Contributor docs for the Qt SDK. Note: These are NOT official API docs; those are found at https://doc.qt.io/
Loading...
Searching...
No Matches
quicklintplugin.cpp
Go to the documentation of this file.
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// Qt-Security score:significant reason:default
4
7#include <QtQmlCompiler/private/qqmlsasourcelocation_p.h>
8#include <QtQmlCompiler/private/qqmljsutils_p.h>
9
11
12using namespace Qt::StringLiterals;
13
14static constexpr QQmlSA::LoggerWarningId quickLayoutPositioning { "Quick.layout-positioning" };
15static constexpr QQmlSA::LoggerWarningId quickAttachedPropertyType { "Quick.attached-property-type" };
16static constexpr QQmlSA::LoggerWarningId quickControlsNativeCustomize { "Quick.controls-native-customize" };
17static constexpr QQmlSA::LoggerWarningId quickAnchorCombinations { "Quick.anchor-combinations" };
18static constexpr QQmlSA::LoggerWarningId quickUnexpectedVarType { "Quick.unexpected-var-type" };
19static constexpr QQmlSA::LoggerWarningId quickPropertyChangesParsed { "Quick.property-changes-parsed" };
20static constexpr QQmlSA::LoggerWarningId quickControlsAttachedPropertyReuse { "Quick.controls-attached-property-reuse" };
21static constexpr QQmlSA::LoggerWarningId quickAttachedPropertyReuse { "Quick.attached-property-reuse" };
22static constexpr QQmlSA::LoggerWarningId quickColor { "Quick.color" };
23static constexpr QQmlSA::LoggerWarningId quickStateNoChildItem { "Quick.state-no-child-item" };
24
30
31void ForbiddenChildrenPropertyValidatorPass::addWarning(QAnyStringView moduleName,
32 QAnyStringView typeName,
33 QAnyStringView propertyName,
34 QAnyStringView warning)
35{
36 auto element = resolveType(moduleName, typeName);
37 if (!element.isNull())
38 m_types[element].append({ propertyName.toString(), warning.toString() });
39}
40
41bool ForbiddenChildrenPropertyValidatorPass::shouldRun(const QQmlSA::Element &element)
42{
43 if (!element.parentScope())
44 return false;
45
46 for (const auto &pair : std::as_const(m_types).asKeyValueRange()) {
47 if (element.parentScope().inherits(pair.first))
48 return true;
49 }
50
51 return false;
52}
53
54void ForbiddenChildrenPropertyValidatorPass::run(const QQmlSA::Element &element)
55{
56 for (const auto &elementPair : std::as_const(m_types).asKeyValueRange()) {
57 const QQmlSA::Element &type = elementPair.first;
58 const QQmlSA::Element parentScope = element.parentScope();
59
60 // If the parent's default property is not what we think it is, then we can't say whether
61 // the element in question is actually a visual child of the (document) parent scope.
62 const QQmlSA::Property defaultProperty
63 = parentScope.property(parentScope.defaultPropertyName());
64 if (defaultProperty != type.property(type.defaultPropertyName()))
65 continue;
66
67 if (!element.parentScope().inherits(type))
68 continue;
69
70 for (const auto &warning : elementPair.second) {
71 if (!element.hasOwnPropertyBindings(warning.propertyName))
72 continue;
73
74 const auto bindings = element.ownPropertyBindings(warning.propertyName);
75 const auto firstBinding = bindings.constBegin().value();
76 emitWarning(warning.message, quickLayoutPositioning, firstBinding.sourceLocation());
77 }
78 break;
79 }
80}
81
86
88 QList<TypeDescription> allowedTypes,
89 bool allowInDelegate, QAnyStringView warning)
90{
91 QVarLengthArray<QQmlSA::Element, 4> elements;
92
93 const QQmlSA::Element attachedType = resolveAttached(attachType.module, attachType.name);
94 if (!attachedType) {
95 emitWarning(
96 "Cannot find attached type for %1/%2"_L1.arg(attachType.module, attachType.name),
97 quickAttachedPropertyType);
98 return QString();
99 }
100
101 for (const TypeDescription &desc : allowedTypes) {
102 const QQmlSA::Element type = resolveType(desc.module, desc.name);
103 if (type.isNull())
104 continue;
105 elements.push_back(type);
106 }
107
108 m_attachedTypes.insert(
109 { std::make_pair<>(attachedType.internalId(),
110 Warning{ elements, allowInDelegate, warning.toString() }) });
111
112 return attachedType.internalId();
113}
114
115void AttachedPropertyTypeValidatorPass::checkWarnings(const QQmlSA::Element &element,
116 const QQmlSA::Element &scopeUsedIn,
117 const QQmlSA::SourceLocation &location)
118{
119 auto warning = m_attachedTypes.constFind(element.internalId());
120 if (warning == m_attachedTypes.cend())
121 return;
122 for (const QQmlSA::Element &type : warning->allowedTypes) {
123 if (scopeUsedIn.inherits(type))
124 return;
125 }
126 // You can use e.g. Layout.leftMargin: 4 in PropertyChanges;
127 // custom parser can do arbitrary things with their contained bindings
128 if ( QQmlJSScope::scope(scopeUsedIn)->isInCustomParserParent() )
129 return;
130
131 if (warning->allowInDelegate) {
132 if (scopeUsedIn.isPropertyRequired(u"index"_s)
133 || scopeUsedIn.isPropertyRequired(u"model"_s))
134 return;
135
136 // If the scope is at the root level, we cannot know whether it will be used
137 // as a delegate or not.
138 if (scopeUsedIn.isFileRootComponent())
139 return;
140
141 for (const QQmlSA::Binding &binding :
142 scopeUsedIn.parentScope().propertyBindings(u"delegate"_s)) {
143 if (!binding.hasObject())
144 continue;
145 if (binding.objectType() == scopeUsedIn)
146 return;
147 }
148 }
149
150 emitWarning(warning->message, quickAttachedPropertyType, location);
151}
152
153void AttachedPropertyTypeValidatorPass::onBinding(const QQmlSA::Element &element,
154 const QString &propertyName,
155 const QQmlSA::Binding &binding,
156 const QQmlSA::Element &bindingScope,
157 const QQmlSA::Element &value)
158{
159 Q_UNUSED(value)
160
161 // We can only analyze simple attached bindings since we don't see
162 // the grouped and attached properties that lead up to this here.
163 //
164 // TODO: This is very crude.
165 // We should add API for grouped and attached properties.
166 if (propertyName.count(QLatin1Char('.')) > 1)
167 return;
168
169 checkWarnings(bindingScope.baseType(), element, binding.sourceLocation());
170}
171
172void AttachedPropertyTypeValidatorPass::onRead(const QQmlSA::Element &element,
173 const QString &propertyName,
174 const QQmlSA::Element &readScope,
175 QQmlSA::SourceLocation location)
176{
177 // If the attachment does not have such a property or method then
178 // it's either a more general error or an enum. Enums are fine.
179 if (element.hasProperty(propertyName) || element.hasMethod(propertyName))
180 checkWarnings(element, readScope, location);
181}
182
183void AttachedPropertyTypeValidatorPass::onWrite(const QQmlSA::Element &element,
184 const QString &propertyName,
185 const QQmlSA::Element &value,
186 const QQmlSA::Element &writeScope,
187 QQmlSA::SourceLocation location)
188{
189 Q_UNUSED(propertyName)
190 Q_UNUSED(value)
191
192 checkWarnings(element, writeScope, location);
193}
194
197{
198 m_elements = {
199 ControlElement { "Control",
200 QStringList { "background", "contentItem", "leftPadding", "rightPadding",
201 "topPadding", "bottomPadding", "horizontalPadding",
202 "verticalPadding", "padding" },
203 false, true },
204 ControlElement { "Button", QStringList { "indicator" } },
205 ControlElement {
206 "ApplicationWindow",
207 QStringList { "background", "contentItem", "header", "footer", "menuBar" } },
208 ControlElement { "ComboBox", QStringList { "indicator" } },
209 ControlElement { "Dial", QStringList { "handle" } },
210 ControlElement { "GroupBox", QStringList { "label" } },
211 ControlElement { "$internal$.QQuickIndicatorButton", QStringList { "indicator" }, false },
212 ControlElement { "Label", QStringList { "background" } },
213 ControlElement { "MenuItem", QStringList { "arrow" } },
214 ControlElement { "Page", QStringList { "header", "footer" } },
215 ControlElement { "Popup", QStringList { "background", "contentItem" } },
216 ControlElement { "RangeSlider", QStringList { "handle" } },
217 ControlElement { "Slider", QStringList { "handle" } },
218 ControlElement { "$internal$.QQuickSwipe",
219 QStringList { "leftItem", "behindItem", "rightItem" }, false },
220 ControlElement { "TextArea", QStringList { "background" } },
221 ControlElement { "TextField", QStringList { "background" } },
222 };
223
224 for (const QString &module : { u"QtQuick.Controls.macOS"_s, u"QtQuick.Controls.Windows"_s }) {
225 if (!manager->hasImportedModule(module))
226 continue;
227
228 QQmlSA::Element control = resolveType(module, "Control");
229
230 for (ControlElement &element : m_elements) {
231 auto type = resolveType(element.isInModuleControls ? module : "QtQuick.Templates",
232 element.name);
233
234 if (type.isNull())
235 continue;
236
237 element.inheritsControl = !element.isControl && type.inherits(control);
238 element.element = type;
239 }
240
241 m_elements.removeIf([](const ControlElement &element) { return element.element.isNull(); });
242
243 break;
244 }
245}
246
247bool ControlsNativeValidatorPass::shouldRun(const QQmlSA::Element &element)
248{
249 for (const ControlElement &controlElement : m_elements) {
250 // If our element inherits control, we don't have to individually check for them here.
251 if (controlElement.inheritsControl)
252 continue;
253 if (element.inherits(controlElement.element))
254 return true;
255 }
256 return false;
257}
258
259void ControlsNativeValidatorPass::run(const QQmlSA::Element &element)
260{
261 for (const ControlElement &controlElement : m_elements) {
262 if (element.inherits(controlElement.element)) {
263 for (const QString &propertyName : controlElement.restrictedProperties) {
264 if (element.hasOwnPropertyBindings(propertyName)) {
265 emitWarning(QStringLiteral("Not allowed to override \"%1\" because native "
266 "styles cannot be customized: See "
267 "https://doc-snapshots.qt.io/qt6-dev/"
268 "qtquickcontrols-customize.html#customization-"
269 "reference for more information.")
270 .arg(propertyName),
271 quickControlsNativeCustomize, element.sourceLocation());
272 }
273 }
274 // Since all the different types we have rules for don't inherit from each other (except
275 // for Control) we don't have to keep checking whether other types match once we've
276 // found one that has been inherited from.
277 if (!controlElement.isControl)
278 break;
279 }
280 }
281}
282
283AnchorsValidatorPass::AnchorsValidatorPass(QQmlSA::PassManager *manager)
285 , m_item(resolveType("QtQuick", "Item"))
286{
287}
288
289bool AnchorsValidatorPass::shouldRun(const QQmlSA::Element &element)
290{
291 return !m_item.isNull() && element.inherits(m_item)
292 && element.hasOwnPropertyBindings(u"anchors"_s);
293}
294
295void AnchorsValidatorPass::run(const QQmlSA::Element &element)
296{
297 enum BindingLocation { Exists = 1, Own = (1 << 1) };
298 QHash<QString, qint8> bindings;
299
300 const QStringList properties = { u"left"_s, u"right"_s, u"horizontalCenter"_s,
301 u"top"_s, u"bottom"_s, u"verticalCenter"_s,
302 u"baseline"_s };
303
304 QList<QQmlSA::Binding> anchorBindings = element.propertyBindings(u"anchors"_s);
305
306 for (qsizetype i = anchorBindings.size() - 1; i >= 0; i--) {
307 auto groupType = anchorBindings[i].groupType();
308 if (groupType.isNull())
309 continue;
310
311 for (const QString &name : properties) {
312
313 const auto &propertyBindings = groupType.ownPropertyBindings(name);
314 if (propertyBindings.begin() == propertyBindings.end())
315 continue;
316
317 bool isUndefined = false;
318 for (const auto &propertyBinding : propertyBindings) {
319 if (propertyBinding.hasUndefinedScriptValue()) {
320 isUndefined = true;
321 break;
322 }
323 }
324
325 if (isUndefined)
326 bindings[name] = 0;
327 else
328 bindings[name] |= Exists | ((i == 0) ? Own : 0);
329 }
330 }
331
332 auto ownSourceLocation = [&](QStringList properties) -> QQmlSA::SourceLocation {
333 QQmlSA::SourceLocation warnLoc;
334
335 for (const QString &name : properties) {
336 if (bindings[name] & Own) {
337 QQmlSA::Element groupType = QQmlSA::Element{ anchorBindings[0].groupType() };
338 auto bindings = groupType.ownPropertyBindings(name);
339 Q_ASSERT(bindings.begin() != bindings.end());
340 warnLoc = bindings.begin().value().sourceLocation();
341 break;
342 }
343 }
344 return warnLoc;
345 };
346
347 if ((bindings[u"left"_s] & bindings[u"right"_s] & bindings[u"horizontalCenter"_s]) & Exists) {
348 QQmlSA::SourceLocation warnLoc =
349 ownSourceLocation({ u"left"_s, u"right"_s, u"horizontalCenter"_s });
350
351 if (warnLoc.isValid()) {
352 emitWarning(
353 "Cannot specify left, right, and horizontalCenter anchors at the same time.",
354 quickAnchorCombinations, warnLoc);
355 }
356 }
357
358 if ((bindings[u"top"_s] & bindings[u"bottom"_s] & bindings[u"verticalCenter"_s]) & Exists) {
359 QQmlSA::SourceLocation warnLoc =
360 ownSourceLocation({ u"top"_s, u"bottom"_s, u"verticalCenter"_s });
361 if (warnLoc.isValid()) {
362 emitWarning("Cannot specify top, bottom, and verticalCenter anchors at the same time.",
363 quickAnchorCombinations, warnLoc);
364 }
365 }
366
367 if ((bindings[u"baseline"_s] & (bindings[u"bottom"_s] | bindings[u"verticalCenter"_s]))
368 & Exists) {
369 QQmlSA::SourceLocation warnLoc =
370 ownSourceLocation({ u"baseline"_s, u"bottom"_s, u"verticalCenter"_s });
371 if (warnLoc.isValid()) {
372 emitWarning("Baseline anchor cannot be used in conjunction with top, bottom, or "
373 "verticalCenter anchors.",
374 quickAnchorCombinations, warnLoc);
375 }
376 }
377}
378
381 , m_swipeDelegate(resolveType("QtQuick.Controls", "SwipeDelegate"))
382{
383}
384
385bool ControlsSwipeDelegateValidatorPass::shouldRun(const QQmlSA::Element &element)
386{
387 return !m_swipeDelegate.isNull() && element.inherits(m_swipeDelegate);
388}
389
390void ControlsSwipeDelegateValidatorPass::run(const QQmlSA::Element &element)
391{
392 for (const auto &property : { u"background"_s, u"contentItem"_s }) {
393 for (const auto &binding : element.ownPropertyBindings(property)) {
394 if (!binding.hasObject())
395 continue;
396 const QQmlSA::Element element = QQmlSA::Element{ binding.objectType() };
397 const auto &bindings = element.propertyBindings(u"anchors"_s);
398 if (bindings.isEmpty())
399 continue;
400
401 if (bindings.first().bindingType() != QQmlSA::BindingType::GroupProperty)
402 continue;
403
404 auto anchors = bindings.first().groupType();
405 for (const auto &disallowed : { u"fill"_s, u"centerIn"_s, u"left"_s, u"right"_s }) {
406 if (anchors.hasPropertyBindings(disallowed)) {
407 QQmlSA::SourceLocation location;
408 const auto &ownBindings = anchors.ownPropertyBindings(disallowed);
409 if (ownBindings.begin() != ownBindings.end()) {
410 location = ownBindings.begin().value().sourceLocation();
411 }
412
413 emitWarning(
414 u"SwipeDelegate: Cannot use horizontal anchors with %1; unable to layout the item."_s
415 .arg(property),
416 quickAnchorCombinations, location);
417 break;
418 }
419 }
420 break;
421 }
422 }
423
424 const auto &swipe = element.ownPropertyBindings(u"swipe"_s);
425 if (swipe.begin() == swipe.end())
426 return;
427
428 const auto firstSwipe = swipe.begin().value();
429 if (firstSwipe.bindingType() != QQmlSA::BindingType::GroupProperty)
430 return;
431
432 auto group = firstSwipe.groupType();
433
434 const std::array ownDirBindings = { group.ownPropertyBindings(u"right"_s),
435 group.ownPropertyBindings(u"left"_s),
436 group.ownPropertyBindings(u"behind"_s) };
437
438 auto ownBindingIterator =
439 std::find_if(ownDirBindings.begin(), ownDirBindings.end(),
440 [](const auto &bindings) { return bindings.begin() != bindings.end(); });
441
442 if (ownBindingIterator == ownDirBindings.end())
443 return;
444
445 if (group.hasPropertyBindings(u"behind"_s)
446 && (group.hasPropertyBindings(u"right"_s) || group.hasPropertyBindings(u"left"_s))) {
447 emitWarning("SwipeDelegate: Cannot set both behind and left/right properties",
448 quickAnchorCombinations, ownBindingIterator->begin().value().sourceLocation());
449 }
450}
451
453 QQmlSA::PassManager *manager,
454 const QMultiHash<QString, TypeDescription> &expectedPropertyTypes)
456{
457 QMultiHash<QString, QQmlSA::Element> propertyTypes;
458
459 for (const auto &pair : expectedPropertyTypes.asKeyValueRange()) {
460 const QQmlSA::Element propType = pair.second.module.isEmpty()
461 ? resolveBuiltinType(pair.second.name)
462 : resolveType(pair.second.module, pair.second.name);
463 if (!propType.isNull())
464 propertyTypes.insert(pair.first, propType);
465 }
466
467 m_expectedPropertyTypes = propertyTypes;
468}
469
470void VarBindingTypeValidatorPass::onBinding(const QQmlSA::Element &element,
471 const QString &propertyName,
472 const QQmlSA::Binding &binding,
473 const QQmlSA::Element &bindingScope,
474 const QQmlSA::Element &value)
475{
476 Q_UNUSED(element);
477 Q_UNUSED(bindingScope);
478
479 const auto range = m_expectedPropertyTypes.equal_range(propertyName);
480
481 if (range.first == range.second)
482 return;
483
484 QQmlSA::Element bindingType;
485
486 if (!value.isNull()) {
487 bindingType = value;
488 } else {
489 if (QQmlSA::Binding::isLiteralBinding(binding.bindingType())) {
490 bindingType = resolveLiteralType(binding);
491 } else {
492 switch (binding.bindingType()) {
493 case QQmlSA::BindingType::Object:
494 bindingType = QQmlSA::Element{ binding.objectType() };
495 break;
497 break;
498 default:
499 return;
500 }
501 }
502 }
503
504 if (std::find_if(range.first, range.second,
505 [&](const QQmlSA::Element &scope) { return bindingType.inherits(scope); })
506 == range.second) {
507
508 const bool bindingTypeIsComposite = bindingType.isComposite();
509 if (bindingTypeIsComposite && !bindingType.baseType()) {
510 /* broken module or missing import, there is nothing we
511 can really check here, as something is amiss. We
512 simply skip this binding, and assume that whatever
513 caused the breakage here will already cause another
514 warning somewhere else.
515 */
516 return;
517 }
518 const QString bindingTypeName =
519 bindingTypeIsComposite ? bindingType.baseType().name()
520 : bindingType.name();
521 QStringList expectedTypeNames;
522
523 for (auto it = range.first; it != range.second; it++)
524 expectedTypeNames << it.value().name();
525
526 emitWarning(u"Unexpected type for property \"%1\" expected %2 got %3"_s.arg(
527 propertyName, expectedTypeNames.join(u", "_s), bindingTypeName),
528 quickUnexpectedVarType, binding.sourceLocation());
529 }
530}
531
533{
534public:
535 ColorValidatorPass(QQmlSA::PassManager *manager);
536
537 void onBinding(const QQmlSA::Element &element, const QString &propertyName,
538 const QQmlSA::Binding &binding, const QQmlSA::Element &bindingScope,
539 const QQmlSA::Element &value) override;
540private:
541 QQmlSA::Element m_colorType;
542
543 static inline const QRegularExpression s_hexPattern{ "^#[0-9A-Fa-f]{6}([0-9A-Fa-f]{2})?$"_L1 };
544 // list taken from https://doc.qt.io/qt-6/qcolor.html#fromString
545 QStringList m_colorNames = {
546 u"aliceblue"_s,
547 u"antiquewhite"_s,
548 u"aqua"_s,
549 u"aquamarine"_s,
550 u"azure"_s,
551 u"beige"_s,
552 u"bisque"_s,
553 u"black"_s,
554 u"blanchedalmond"_s,
555 u"blue"_s,
556 u"blueviolet"_s,
557 u"brown"_s,
558 u"burlywood"_s,
559 u"cadetblue"_s,
560 u"chartreuse"_s,
561 u"chocolate"_s,
562 u"coral"_s,
563 u"cornflowerblue"_s,
564 u"cornsilk"_s,
565 u"crimson"_s,
566 u"cyan"_s,
567 u"darkblue"_s,
568 u"darkcyan"_s,
569 u"darkgoldenrod"_s,
570 u"darkgray"_s,
571 u"darkgreen"_s,
572 u"darkgrey"_s,
573 u"darkkhaki"_s,
574 u"darkmagenta"_s,
575 u"darkolivegreen"_s,
576 u"darkorange"_s,
577 u"darkorchid"_s,
578 u"darkred"_s,
579 u"darksalmon"_s,
580 u"darkseagreen"_s,
581 u"darkslateblue"_s,
582 u"darkslategray"_s,
583 u"darkslategrey"_s,
584 u"darkturquoise"_s,
585 u"darkviolet"_s,
586 u"deeppink"_s,
587 u"deepskyblue"_s,
588 u"dimgray"_s,
589 u"dimgrey"_s,
590 u"dodgerblue"_s,
591 u"firebrick"_s,
592 u"floralwhite"_s,
593 u"forestgreen"_s,
594 u"fuchsia"_s,
595 u"gainsboro"_s,
596 u"ghostwhite"_s,
597 u"gold"_s,
598 u"goldenrod"_s,
599 u"gray"_s,
600 u"green"_s,
601 u"greenyellow"_s,
602 u"grey"_s,
603 u"honeydew"_s,
604 u"hotpink"_s,
605 u"indianred"_s,
606 u"indigo"_s,
607 u"ivory"_s,
608 u"khaki"_s,
609 u"lavender"_s,
610 u"lavenderblush"_s,
611 u"lawngreen"_s,
612 u"lemonchiffon"_s,
613 u"lightblue"_s,
614 u"lightcoral"_s,
615 u"lightcyan"_s,
616 u"lightgoldenrodyellow"_s,
617 u"lightgray"_s,
618 u"lightgreen"_s,
619 u"lightgrey"_s,
620 u"lightpink"_s,
621 u"lightsalmon"_s,
622 u"lightseagreen"_s,
623 u"lightskyblue"_s,
624 u"lightslategray"_s,
625 u"lightslategrey"_s,
626 u"lightsteelblue"_s,
627 u"lightyellow"_s,
628 u"lime"_s,
629 u"limegreen"_s,
630 u"linen"_s,
631 u"magenta"_s,
632 u"maroon"_s,
633 u"mediumaquamarine"_s,
634 u"mediumblue"_s,
635 u"mediumorchid"_s,
636 u"mediumpurple"_s,
637 u"mediumseagreen"_s,
638 u"mediumslateblue"_s,
639 u"mediumspringgreen"_s,
640 u"mediumturquoise"_s,
641 u"mediumvioletred"_s,
642 u"midnightblue"_s,
643 u"mintcream"_s,
644 u"mistyrose"_s,
645 u"moccasin"_s,
646 u"navajowhite"_s,
647 u"navy"_s,
648 u"oldlace"_s,
649 u"olive"_s,
650 u"olivedrab"_s,
651 u"orange"_s,
652 u"orangered"_s,
653 u"orchid"_s,
654 u"palegoldenrod"_s,
655 u"palegreen"_s,
656 u"paleturquoise"_s,
657 u"palevioletred"_s,
658 u"papayawhip"_s,
659 u"peachpuff"_s,
660 u"peru"_s,
661 u"pink"_s,
662 u"plum"_s,
663 u"powderblue"_s,
664 u"purple"_s,
665 u"red"_s,
666 u"rosybrown"_s,
667 u"royalblue"_s,
668 u"saddlebrown"_s,
669 u"salmon"_s,
670 u"sandybrown"_s,
671 u"seagreen"_s,
672 u"seashell"_s,
673 u"sienna"_s,
674 u"silver"_s,
675 u"skyblue"_s,
676 u"slateblue"_s,
677 u"slategray"_s,
678 u"slategrey"_s,
679 u"snow"_s,
680 u"springgreen"_s,
681 u"steelblue"_s,
682 u"tan"_s,
683 u"teal"_s,
684 u"thistle"_s,
685 u"tomato"_s,
686 u"turquoise"_s,
687 u"violet"_s,
688 u"wheat"_s,
689 u"white"_s,
690 u"whitesmoke"_s,
691 u"yellow"_s,
692 u"yellowgreen"_s,
693 };
694};
695
696
697ColorValidatorPass::ColorValidatorPass(QQmlSA::PassManager *manager)
698 : PropertyPass(manager), m_colorType(resolveType("QtQuick"_L1, "color"_L1))
699{
700 Q_ASSERT_X(std::is_sorted(m_colorNames.cbegin(), m_colorNames.cend()), "ColorValidatorPass",
701 "m_colorNames should be sorted!");
702}
703
704void ColorValidatorPass::onBinding(const QQmlSA::Element &element, const QString &propertyName,
705 const QQmlSA::Binding &binding, const QQmlSA::Element &,
706 const QQmlSA::Element &)
707{
708 if (binding.bindingType() != QQmlSA::BindingType::StringLiteral)
709 return;
710 const auto propertyType = element.property(propertyName).type();
711 if (!propertyType || propertyType != m_colorType)
712 return;
713
714 QString colorName = binding.stringValue();
715 // for "named" colors, QColor::fromString does not care about
716 // the case
717 if (!colorName.startsWith(u'#'))
718 colorName = std::move(colorName).toLower();
719 if (s_hexPattern.match(colorName).hasMatch())
720 return;
721
722 if (std::binary_search(m_colorNames.cbegin(), m_colorNames.cend(), colorName))
723 return;
724
725 if (colorName == u"transparent")
726 return;
727
728 auto suggestion = QQmlJSUtils::didYouMean(
729 colorName, m_colorNames,
730 QQmlSA::SourceLocationPrivate::sourceLocation(binding.sourceLocation()));
731
732 emitWarningWithOptionalFix(*this, "Invalid color \"%1\"."_L1.arg(colorName), quickColor,
733 binding.sourceLocation(), suggestion);
734}
735
736void AttachedPropertyReuse::onRead(const QQmlSA::Element &element, const QString &propertyName,
737 const QQmlSA::Element &readScope,
738 QQmlSA::SourceLocation location)
739{
740 const auto range = usedAttachedTypes.equal_range(readScope);
741 const auto attachedTypeAndLocation = std::find_if(
742 range.first, range.second, [&](const ElementAndLocation &elementAndLocation) {
743 return elementAndLocation.element == element;
744 });
745 if (attachedTypeAndLocation != range.second) {
746 const QQmlSA::SourceLocation attachedLocation = attachedTypeAndLocation->location;
747
748 // Ignore enum accesses, as these will not cause the attached object to be created.
749 // Also ignore anything we cannot determine.
750 if (!element.hasProperty(propertyName) && !element.hasMethod(propertyName))
751 return;
752
753 for (QQmlSA::Element scope = readScope.parentScope(); !scope.isNull();
754 scope = scope.parentScope()) {
755 const auto range = usedAttachedTypes.equal_range(scope);
756 bool found = false;
757 for (auto it = range.first; it != range.second; ++it) {
758 if (it->element == element) {
759 found = true;
760 break;
761 }
762 }
763 if (!found)
764 continue;
765
766 const QString id = resolveElementToId(scope, readScope);
767 const QQmlSA::SourceLocation idInsertLocation{ attachedLocation.offset(), 0,
768 attachedLocation.startLine(),
769 attachedLocation.startColumn() };
770 QQmlSA::FixSuggestion suggestion{ "Reference it by id instead:"_L1, idInsertLocation,
771 id.isEmpty() ? u"<id>."_s : (id + '.'_L1) };
772
773 if (id.isEmpty())
774 suggestion.setHint("You first have to give the element an id"_L1);
775 else
776 suggestion.setAutoApplicable();
777
778 emitWarning("Using attached type %1 already initialized in a parent scope."_L1.arg(
779 element.name()),
780 category, attachedLocation, suggestion);
781 return;
782 }
783
784 return;
785 }
786
787 if (element.hasProperty(propertyName))
788 return; // an actual property
789
790 QQmlSA::Element type = resolveTypeInFileScope(propertyName);
791 QQmlSA::Element attached = resolveAttachedInFileScope(propertyName);
792 if (!type || !attached)
793 return;
794
795 if (category == quickControlsAttachedPropertyReuse) {
796 for (QQmlSA::Element parent = attached; parent; parent = parent.baseType()) {
797 // ### TODO: Make it possible to resolve QQuickAttachedPropertyPropagator
798 // so that we don't have to compare the internal id
799 if (parent.internalId() == "QQuickAttachedPropertyPropagator"_L1) {
800 usedAttachedTypes.insert(readScope, {attached, location});
801 break;
802 }
803 }
804
805 } else {
806 usedAttachedTypes.insert(readScope, {attached, location});
807 }
808}
809
810void AttachedPropertyReuse::onWrite(const QQmlSA::Element &element, const QString &propertyName,
811 const QQmlSA::Element &value, const QQmlSA::Element &writeScope,
812 QQmlSA::SourceLocation location)
813{
814 Q_UNUSED(value);
815 onRead(element, propertyName, writeScope, location);
816}
817
818void QmlLintQuickPlugin::registerPasses(QQmlSA::PassManager *manager,
819 const QQmlSA::Element &rootElement)
820{
821 const QQmlSA::LoggerWarningId attachedReuseCategory = [manager]() {
822 if (manager->isCategoryEnabled(quickAttachedPropertyReuse))
823 return quickAttachedPropertyReuse;
824 if (manager->isCategoryEnabled(qmlAttachedPropertyReuse))
825 return qmlAttachedPropertyReuse;
826 return quickControlsAttachedPropertyReuse;
827 }();
828
829 const bool hasQuick = manager->hasImportedModule("QtQuick");
830 const bool hasQuickLayouts = manager->hasImportedModule("QtQuick.Layouts");
831 const bool hasQuickControls = manager->hasImportedModule("QtQuick.Templates")
832 || manager->hasImportedModule("QtQuick.Controls")
833 || manager->hasImportedModule("QtQuick.Controls.Basic");
834
835 Q_UNUSED(rootElement);
836
837 if (hasQuick) {
838 manager->registerElementPass(std::make_unique<AnchorsValidatorPass>(manager));
839 manager->registerElementPass(std::make_unique<PropertyChangesValidatorPass>(manager));
840 manager->registerElementPass(std::make_unique<StateNoItemChildrenValidator>(manager));
841 manager->registerPropertyPass(std::make_unique<QQuickLiteralBindingCheck>(manager),
842 QAnyStringView(), QAnyStringView());
843 manager->registerPropertyPass(std::make_unique<ColorValidatorPass>(manager),
844 QAnyStringView(), QAnyStringView());
845
846 auto forbiddenChildProperty =
847 std::make_unique<ForbiddenChildrenPropertyValidatorPass>(manager);
848
849 for (const QString &element : { u"Grid"_s, u"Flow"_s }) {
850 for (const QString &property : { u"anchors"_s, u"x"_s, u"y"_s }) {
851 forbiddenChildProperty->addWarning(
852 "QtQuick", element, property,
853 u"Cannot specify %1 for items inside %2. %2 will not function."_s.arg(
854 property, element));
855 }
856 }
857
858 if (hasQuickLayouts) {
859 forbiddenChildProperty->addWarning(
860 "QtQuick.Layouts", "Layout", "anchors",
861 "Detected anchors on an item that is managed by a layout. This is undefined "
862 u"behavior; use Layout.alignment instead.");
863 forbiddenChildProperty->addWarning(
864 "QtQuick.Layouts", "Layout", "x",
865 "Detected x on an item that is managed by a layout. This is undefined "
866 u"behavior; use Layout.leftMargin or Layout.rightMargin instead.");
867 forbiddenChildProperty->addWarning(
868 "QtQuick.Layouts", "Layout", "y",
869 "Detected y on an item that is managed by a layout. This is undefined "
870 u"behavior; use Layout.topMargin or Layout.bottomMargin instead.");
871 forbiddenChildProperty->addWarning(
872 "QtQuick.Layouts", "Layout", "width",
873 "Detected width on an item that is managed by a layout. This is undefined "
874 u"behavior; use implicitWidth or Layout.preferredWidth instead.");
875 forbiddenChildProperty->addWarning(
876 "QtQuick.Layouts", "Layout", "height",
877 "Detected height on an item that is managed by a layout. This is undefined "
878 u"behavior; use implictHeight or Layout.preferredHeight instead.");
879 }
880
881 manager->registerElementPass(std::move(forbiddenChildProperty));
882 }
883
884 auto attachedPropertyType = std::make_shared<AttachedPropertyTypeValidatorPass>(manager);
885
886 auto addAttachedWarning = [&](TypeDescription attachedType, QList<TypeDescription> allowedTypes,
887 QAnyStringView warning, bool allowInDelegate = false) {
888 QString attachedTypeName = attachedPropertyType->addWarning(attachedType, allowedTypes,
889 allowInDelegate, warning);
890 if (attachedTypeName.isEmpty())
891 return;
892
893 manager->registerPropertyPass(attachedPropertyType, attachedType.module,
894 u"$internal$."_s + attachedTypeName, {}, false);
895 };
896
897 auto addVarBindingWarning =
898 [&](QAnyStringView moduleName, QAnyStringView typeName,
899 const QMultiHash<QString, TypeDescription> &expectedPropertyTypes) {
900 auto varBindingType = std::make_shared<VarBindingTypeValidatorPass>(
901 manager, expectedPropertyTypes);
902 for (const auto &propertyName : expectedPropertyTypes.uniqueKeys()) {
903 manager->registerPropertyPass(varBindingType, moduleName, typeName,
904 propertyName);
905 }
906 };
907
908 if (hasQuick) {
909 addVarBindingWarning("QtQuick", "TableView",
910 { { "columnWidthProvider", { "", "function" } },
911 { "rowHeightProvider", { "", "function" } } });
912 addAttachedWarning({ "QtQuick", "Accessible" }, { { "QtQuick", "Item" } },
913 "Accessible attached property must be attached to an object deriving from Item or Action");
914 addAttachedWarning({ "QtQuick", "LayoutMirroring" },
915 { { "QtQuick", "Item" }, { "QtQuick", "Window" } },
916 "LayoutMirroring attached property must be attached to an object deriving from Item or Window");
917 addAttachedWarning({ "QtQuick", "EnterKey" }, { { "QtQuick", "Item" } },
918 "EnterKey attached property must be attached to an object deriving from Item");
919 }
920 if (hasQuickLayouts) {
921 addAttachedWarning({ "QtQuick.Layouts", "Layout" }, { { "QtQuick", "Item" } },
922 "Layout attached property must be attached to an object deriving from Item");
923 addAttachedWarning({ "QtQuick.Layouts", "StackLayout" }, { { "QtQuick", "Item" } },
924 "StackLayout attached property must be attached to an object deriving from Item");
925 }
926
927
928 if (hasQuickControls) {
929 manager->registerElementPass(std::make_unique<ControlsSwipeDelegateValidatorPass>(manager));
930 manager->registerPropertyPass(std::make_unique<AttachedPropertyReuse>(
931 manager, attachedReuseCategory), "", "");
932
933 addAttachedWarning({ "QtQuick.Templates", "ScrollBar" },
934 { { "QtQuick", "Flickable" }, { "QtQuick.Templates", "ScrollView" } },
935 "ScrollBar attached property must be attached to an object deriving from Flickable or ScrollView");
936 addAttachedWarning({ "QtQuick.Templates", "ScrollIndicator" },
937 { { "QtQuick", "Flickable" } },
938 "ScrollIndicator attached property must be attached to an object deriving from Flickable");
939 addAttachedWarning({ "QtQuick.Templates", "TextArea" }, { { "QtQuick", "Flickable" } },
940 "TextArea attached property must be attached to an object deriving from Flickable");
941 addAttachedWarning({ "QtQuick.Templates", "SplitView" }, { { "QtQuick", "Item" } },
942 "SplitView attached property must be attached to an object deriving from Item");
943 addAttachedWarning({ "QtQuick.Templates", "StackView" }, { { "QtQuick", "Item" } },
944 "StackView attached property must be attached to an object deriving from Item");
945 addAttachedWarning({ "QtQuick.Templates", "ToolTip" }, { { "QtQuick", "Item" } },
946 "ToolTip attached property must be attached to an object deriving from Item");
947 addAttachedWarning({ "QtQuick.Templates", "SwipeDelegate" }, { { "QtQuick", "Item" } },
948 "SwipeDelegate attached property must be attached to an object deriving from Item");
949 addAttachedWarning({ "QtQuick.Templates", "SwipeView" }, { { "QtQuick", "Item" } },
950 "SwipeView attached property must be attached to an object deriving from Item");
951 addVarBindingWarning("QtQuick.Templates", "Tumbler",
952 { { "contentItem", { "QtQuick", "PathView" } },
953 { "contentItem", { "QtQuick", "ListView" } } });
954 addVarBindingWarning("QtQuick.Templates", "SpinBox",
955 { { "textFromValue", { "", "function" } },
956 { "valueFromText", { "", "function" } } });
957 } else if (attachedReuseCategory != quickControlsAttachedPropertyReuse) {
958 manager->registerPropertyPass(std::make_unique<AttachedPropertyReuse>(
959 manager, attachedReuseCategory), "", "");
960 }
961
962 if (manager->hasImportedModule(u"QtQuick.Controls.macOS"_s)
963 || manager->hasImportedModule(u"QtQuick.Controls.Windows"_s))
964 manager->registerElementPass(std::make_unique<ControlsNativeValidatorPass>(manager));
965}
966
969 , m_propertyChanges(resolveType("QtQuick", "PropertyChanges"))
970{
971}
972
973bool PropertyChangesValidatorPass::shouldRun(const QQmlSA::Element &element)
974{
975 return !m_propertyChanges.isNull() && element.inherits(m_propertyChanges);
976}
977
978void PropertyChangesValidatorPass::run(const QQmlSA::Element &element)
979{
980 const QQmlSA::Binding::Bindings bindings = element.ownPropertyBindings();
981
982 const auto target =
983 std::find_if(bindings.constBegin(), bindings.constEnd(),
984 [](const auto binding) { return binding.propertyName() == u"target"_s; });
985 if (target == bindings.constEnd())
986 return;
987
988 QString targetId = u"<id>"_s;
989 const auto targetLocation = target.value().sourceLocation();
990 const QString targetBinding = sourceCode(targetLocation);
991 const QQmlSA::Element targetElement = resolveIdToElement(targetBinding, element);
992 if (!targetElement.isNull())
993 targetId = targetBinding;
994
995 bool hadCustomParsedBindings = false;
996 for (auto it = bindings.constBegin(); it != bindings.constEnd(); ++it) {
997 const auto &propertyName = it.key();
998 const auto &propertyBinding = it.value();
999 if (element.hasProperty(propertyName))
1000 continue;
1001
1002 const QQmlSA::SourceLocation bindingLocation = propertyBinding.sourceLocation();
1003 if (!targetElement.isNull() && !targetElement.hasProperty(propertyName)) {
1004 emitWarning(
1005 "Unknown property \"%1\" in PropertyChanges."_L1.arg(propertyName),
1006 quickPropertyChangesParsed, bindingLocation);
1007 continue;
1008 }
1009
1010 QString binding = sourceCode(bindingLocation);
1011 if (binding.length() > 16)
1012 binding = binding.left(13) + "..."_L1;
1013
1014 hadCustomParsedBindings = true;
1015 emitWarning("Property \"%1\" is custom-parsed in PropertyChanges. "
1016 "You should phrase this binding as \"%2.%1: %3\""_L1.arg(propertyName, targetId,
1017 binding),
1018 quickPropertyChangesParsed, bindingLocation);
1019 }
1020
1021 if (hadCustomParsedBindings && !targetElement.isNull()) {
1022 emitWarning("You should remove any bindings on the \"target\" property and avoid "
1023 "custom-parsed bindings in PropertyChanges.",
1024 quickPropertyChangesParsed, targetLocation);
1025 }
1026}
1027
1030 , m_state(resolveType("QtQuick", "State"))
1031 , m_anchorChanges(resolveType("QtQuick", "AnchorChanges"))
1032 , m_parentChanges(resolveType("QtQuick", "ParentChange"))
1033 , m_propertyChanges(resolveType("QtQuick", "PropertyChanges"))
1034 , m_stateChangeScript(resolveType("QtQuick", "StateChangeScript"))
1035{}
1036
1037bool StateNoItemChildrenValidator::shouldRun(const QQmlSA::Element &element)
1038{
1039 return element.inherits(m_state);
1040}
1041
1042void StateNoItemChildrenValidator::run(const QQmlSA::Element &element)
1043{
1044 const auto &childScopes = QQmlJSScope::scope(element)->childScopes();
1045 for (const auto &child : childScopes) {
1046 if (child->scopeType() != QQmlSA::ScopeType::QMLScope)
1047 continue;
1048
1049 if (child->inherits(QQmlJSScope::scope(m_anchorChanges))
1050 || child->inherits(QQmlJSScope::scope(m_parentChanges))
1051 || child->inherits(QQmlJSScope::scope(m_propertyChanges))
1052 || child->inherits(QQmlJSScope::scope(m_stateChangeScript))) {
1053 continue;
1054 }
1055 QString msg = "A State cannot have a child item of type %1"_L1.arg(child->baseTypeName());
1056 auto loc = QQmlSA::SourceLocationPrivate::createQQmlSASourceLocation(
1057 child->sourceLocation());
1058 emitWarning(msg, quickStateNoChildItem, loc);
1059 }
1060}
1061
1062QT_END_NAMESPACE
1063
1064#include "moc_quicklintplugin.cpp"
bool shouldRun(const QQmlSA::Element &element) override
Controls whether the run() function should be executed on the given element.
AnchorsValidatorPass(QQmlSA::PassManager *manager)
void run(const QQmlSA::Element &element) override
Executes if shouldRun() returns true.
void onRead(const QQmlSA::Element &element, const QString &propertyName, const QQmlSA::Element &readScope, QQmlSA::SourceLocation location) override
Executes whenever a property is read.
void onWrite(const QQmlSA::Element &element, const QString &propertyName, const QQmlSA::Element &value, const QQmlSA::Element &writeScope, QQmlSA::SourceLocation location) override
Executes whenever a property is written to.
void onRead(const QQmlSA::Element &element, const QString &propertyName, const QQmlSA::Element &readScope, QQmlSA::SourceLocation location) override
Executes whenever a property is read.
AttachedPropertyTypeValidatorPass(QQmlSA::PassManager *manager)
void onWrite(const QQmlSA::Element &element, const QString &propertyName, const QQmlSA::Element &value, const QQmlSA::Element &writeScope, QQmlSA::SourceLocation location) override
Executes whenever a property is written to.
void onBinding(const QQmlSA::Element &element, const QString &propertyName, const QQmlSA::Binding &binding, const QQmlSA::Element &bindingScope, const QQmlSA::Element &value) override
Executes whenever a property gets bound to a value.
QString addWarning(TypeDescription attachType, QList< TypeDescription > allowedTypes, bool allowInDelegate, QAnyStringView warning)
ColorValidatorPass(QQmlSA::PassManager *manager)
void onBinding(const QQmlSA::Element &element, const QString &propertyName, const QQmlSA::Binding &binding, const QQmlSA::Element &bindingScope, const QQmlSA::Element &value) override
Executes whenever a property gets bound to a value.
ControlsNativeValidatorPass(QQmlSA::PassManager *manager)
void run(const QQmlSA::Element &element) override
Executes if shouldRun() returns true.
bool shouldRun(const QQmlSA::Element &element) override
Controls whether the run() function should be executed on the given element.
bool shouldRun(const QQmlSA::Element &element) override
Controls whether the run() function should be executed on the given element.
ControlsSwipeDelegateValidatorPass(QQmlSA::PassManager *manager)
void run(const QQmlSA::Element &element) override
Executes if shouldRun() returns true.
void addWarning(QAnyStringView moduleName, QAnyStringView typeName, QAnyStringView propertyName, QAnyStringView warning)
ForbiddenChildrenPropertyValidatorPass(QQmlSA::PassManager *manager)
void run(const QQmlSA::Element &element) override
Executes if shouldRun() returns true.
bool shouldRun(const QQmlSA::Element &element) override
Controls whether the run() function should be executed on the given element.
void run(const QQmlSA::Element &element) override
Executes if shouldRun() returns true.
PropertyChangesValidatorPass(QQmlSA::PassManager *manager)
bool shouldRun(const QQmlSA::Element &element) override
Controls whether the run() function should be executed on the given element.
void run(const QQmlSA::Element &element) override
Executes if shouldRun() returns true.
StateNoItemChildrenValidator(QQmlSA::PassManager *manager)
bool shouldRun(const QQmlSA::Element &element) override
Controls whether the run() function should be executed on the given element.
VarBindingTypeValidatorPass(QQmlSA::PassManager *manager, const QMultiHash< QString, TypeDescription > &expectedPropertyTypes)
void onBinding(const QQmlSA::Element &element, const QString &propertyName, const QQmlSA::Binding &binding, const QQmlSA::Element &bindingScope, const QQmlSA::Element &value) override
Executes whenever a property gets bound to a value.
\inmodule QtQmlCompiler
Combined button and popup list for selecting options.
static constexpr QQmlSA::LoggerWarningId quickControlsAttachedPropertyReuse
static constexpr QQmlSA::LoggerWarningId quickControlsNativeCustomize
static constexpr QQmlSA::LoggerWarningId quickStateNoChildItem
static constexpr QQmlSA::LoggerWarningId quickAttachedPropertyType
static constexpr QQmlSA::LoggerWarningId quickUnexpectedVarType
static constexpr QQmlSA::LoggerWarningId quickAttachedPropertyReuse
static constexpr QQmlSA::LoggerWarningId quickPropertyChangesParsed
static constexpr QQmlSA::LoggerWarningId quickAnchorCombinations
static constexpr QQmlSA::LoggerWarningId quickLayoutPositioning
static constexpr QQmlSA::LoggerWarningId quickColor