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 // we support both long and short hex codes for both RGB and ARGB,
543 // so 3, 4, 6 and 8 hex digits are allowed
544 static inline const QRegularExpression s_hexPattern{ "^#((([0-9A-Fa-f]{3}){1,2})|(([0-9A-Fa-f]{4}){1,2}))$"_L1 };
545 // list taken from https://doc.qt.io/qt-6/qcolor.html#fromString
546 QStringList m_colorNames = {
547 u"aliceblue"_s,
548 u"antiquewhite"_s,
549 u"aqua"_s,
550 u"aquamarine"_s,
551 u"azure"_s,
552 u"beige"_s,
553 u"bisque"_s,
554 u"black"_s,
555 u"blanchedalmond"_s,
556 u"blue"_s,
557 u"blueviolet"_s,
558 u"brown"_s,
559 u"burlywood"_s,
560 u"cadetblue"_s,
561 u"chartreuse"_s,
562 u"chocolate"_s,
563 u"coral"_s,
564 u"cornflowerblue"_s,
565 u"cornsilk"_s,
566 u"crimson"_s,
567 u"cyan"_s,
568 u"darkblue"_s,
569 u"darkcyan"_s,
570 u"darkgoldenrod"_s,
571 u"darkgray"_s,
572 u"darkgreen"_s,
573 u"darkgrey"_s,
574 u"darkkhaki"_s,
575 u"darkmagenta"_s,
576 u"darkolivegreen"_s,
577 u"darkorange"_s,
578 u"darkorchid"_s,
579 u"darkred"_s,
580 u"darksalmon"_s,
581 u"darkseagreen"_s,
582 u"darkslateblue"_s,
583 u"darkslategray"_s,
584 u"darkslategrey"_s,
585 u"darkturquoise"_s,
586 u"darkviolet"_s,
587 u"deeppink"_s,
588 u"deepskyblue"_s,
589 u"dimgray"_s,
590 u"dimgrey"_s,
591 u"dodgerblue"_s,
592 u"firebrick"_s,
593 u"floralwhite"_s,
594 u"forestgreen"_s,
595 u"fuchsia"_s,
596 u"gainsboro"_s,
597 u"ghostwhite"_s,
598 u"gold"_s,
599 u"goldenrod"_s,
600 u"gray"_s,
601 u"green"_s,
602 u"greenyellow"_s,
603 u"grey"_s,
604 u"honeydew"_s,
605 u"hotpink"_s,
606 u"indianred"_s,
607 u"indigo"_s,
608 u"ivory"_s,
609 u"khaki"_s,
610 u"lavender"_s,
611 u"lavenderblush"_s,
612 u"lawngreen"_s,
613 u"lemonchiffon"_s,
614 u"lightblue"_s,
615 u"lightcoral"_s,
616 u"lightcyan"_s,
617 u"lightgoldenrodyellow"_s,
618 u"lightgray"_s,
619 u"lightgreen"_s,
620 u"lightgrey"_s,
621 u"lightpink"_s,
622 u"lightsalmon"_s,
623 u"lightseagreen"_s,
624 u"lightskyblue"_s,
625 u"lightslategray"_s,
626 u"lightslategrey"_s,
627 u"lightsteelblue"_s,
628 u"lightyellow"_s,
629 u"lime"_s,
630 u"limegreen"_s,
631 u"linen"_s,
632 u"magenta"_s,
633 u"maroon"_s,
634 u"mediumaquamarine"_s,
635 u"mediumblue"_s,
636 u"mediumorchid"_s,
637 u"mediumpurple"_s,
638 u"mediumseagreen"_s,
639 u"mediumslateblue"_s,
640 u"mediumspringgreen"_s,
641 u"mediumturquoise"_s,
642 u"mediumvioletred"_s,
643 u"midnightblue"_s,
644 u"mintcream"_s,
645 u"mistyrose"_s,
646 u"moccasin"_s,
647 u"navajowhite"_s,
648 u"navy"_s,
649 u"oldlace"_s,
650 u"olive"_s,
651 u"olivedrab"_s,
652 u"orange"_s,
653 u"orangered"_s,
654 u"orchid"_s,
655 u"palegoldenrod"_s,
656 u"palegreen"_s,
657 u"paleturquoise"_s,
658 u"palevioletred"_s,
659 u"papayawhip"_s,
660 u"peachpuff"_s,
661 u"peru"_s,
662 u"pink"_s,
663 u"plum"_s,
664 u"powderblue"_s,
665 u"purple"_s,
666 u"red"_s,
667 u"rosybrown"_s,
668 u"royalblue"_s,
669 u"saddlebrown"_s,
670 u"salmon"_s,
671 u"sandybrown"_s,
672 u"seagreen"_s,
673 u"seashell"_s,
674 u"sienna"_s,
675 u"silver"_s,
676 u"skyblue"_s,
677 u"slateblue"_s,
678 u"slategray"_s,
679 u"slategrey"_s,
680 u"snow"_s,
681 u"springgreen"_s,
682 u"steelblue"_s,
683 u"tan"_s,
684 u"teal"_s,
685 u"thistle"_s,
686 u"tomato"_s,
687 u"turquoise"_s,
688 u"violet"_s,
689 u"wheat"_s,
690 u"white"_s,
691 u"whitesmoke"_s,
692 u"yellow"_s,
693 u"yellowgreen"_s,
694 };
695};
696
697
698ColorValidatorPass::ColorValidatorPass(QQmlSA::PassManager *manager)
699 : PropertyPass(manager), m_colorType(resolveType("QtQuick"_L1, "color"_L1))
700{
701 Q_ASSERT_X(std::is_sorted(m_colorNames.cbegin(), m_colorNames.cend()), "ColorValidatorPass",
702 "m_colorNames should be sorted!");
703}
704
705void ColorValidatorPass::onBinding(const QQmlSA::Element &element, const QString &propertyName,
706 const QQmlSA::Binding &binding, const QQmlSA::Element &,
707 const QQmlSA::Element &)
708{
709 if (binding.bindingType() != QQmlSA::BindingType::StringLiteral)
710 return;
711 const auto propertyType = element.property(propertyName).type();
712 if (!propertyType || propertyType != m_colorType)
713 return;
714
715 QString colorName = binding.stringValue();
716 // for "named" colors, QColor::fromString does not care about
717 // the case
718 if (!colorName.startsWith(u'#'))
719 colorName = std::move(colorName).toLower();
720 if (s_hexPattern.match(colorName).hasMatch())
721 return;
722
723 if (std::binary_search(m_colorNames.cbegin(), m_colorNames.cend(), colorName))
724 return;
725
726 if (colorName == u"transparent")
727 return;
728
729 auto suggestion = QQmlJSUtils::didYouMean(
730 colorName, m_colorNames,
731 QQmlSA::SourceLocationPrivate::sourceLocation(binding.sourceLocation()));
732
733 emitWarningWithOptionalFix(*this, "Invalid color \"%1\"."_L1.arg(colorName), quickColor,
734 binding.sourceLocation(), suggestion);
735}
736
737void AttachedPropertyReuse::onRead(const QQmlSA::Element &element, const QString &propertyName,
738 const QQmlSA::Element &readScope,
739 QQmlSA::SourceLocation location)
740{
741 const auto range = usedAttachedTypes.equal_range(readScope);
742 const auto attachedTypeAndLocation = std::find_if(
743 range.first, range.second, [&](const ElementAndLocation &elementAndLocation) {
744 return elementAndLocation.element == element;
745 });
746 if (attachedTypeAndLocation != range.second) {
747 const QQmlSA::SourceLocation attachedLocation = attachedTypeAndLocation->location;
748
749 // Ignore enum accesses, as these will not cause the attached object to be created.
750 // Also ignore anything we cannot determine.
751 if (!element.hasProperty(propertyName) && !element.hasMethod(propertyName))
752 return;
753
754 for (QQmlSA::Element scope = readScope.parentScope(); !scope.isNull();
755 scope = scope.parentScope()) {
756 const auto range = usedAttachedTypes.equal_range(scope);
757 bool found = false;
758 for (auto it = range.first; it != range.second; ++it) {
759 if (it->element == element) {
760 found = true;
761 break;
762 }
763 }
764 if (!found)
765 continue;
766
767 const QString id = resolveElementToId(scope, readScope);
768 const QQmlSA::SourceLocation idInsertLocation{ attachedLocation.offset(), 0,
769 attachedLocation.startLine(),
770 attachedLocation.startColumn() };
771 QString m = "Reference it by id instead%1:"_L1;
772 m = m.arg(id.isEmpty() ? " (You first have to give the element and id)"_L1 : ""_L1);
773 QQmlSA::FixSuggestion suggestion{ m, idInsertLocation,
774 id.isEmpty() ? u"<id>."_s : (id + '.'_L1) };
775
776 if (!id.isEmpty())
777 suggestion.setAutoApplicable();
778
779 emitWarning("Using attached type %1 already initialized in a parent scope."_L1.arg(
780 element.name()),
781 category, attachedLocation, suggestion);
782 return;
783 }
784
785 return;
786 }
787
788 if (element.hasProperty(propertyName))
789 return; // an actual property
790
791 QQmlSA::Element type = resolveTypeInFileScope(propertyName);
792 QQmlSA::Element attached = resolveAttachedInFileScope(propertyName);
793 if (!type || !attached)
794 return;
795
796 if (category == quickControlsAttachedPropertyReuse) {
797 for (QQmlSA::Element parent = attached; parent; parent = parent.baseType()) {
798 // ### TODO: Make it possible to resolve QQuickAttachedPropertyPropagator
799 // so that we don't have to compare the internal id
800 if (parent.internalId() == "QQuickAttachedPropertyPropagator"_L1) {
801 usedAttachedTypes.insert(readScope, {attached, location});
802 break;
803 }
804 }
805
806 } else {
807 usedAttachedTypes.insert(readScope, {attached, location});
808 }
809}
810
811void AttachedPropertyReuse::onWrite(const QQmlSA::Element &element, const QString &propertyName,
812 const QQmlSA::Element &value, const QQmlSA::Element &writeScope,
813 QQmlSA::SourceLocation location)
814{
815 Q_UNUSED(value);
816 onRead(element, propertyName, writeScope, location);
817}
818
819void QmlLintQuickPlugin::registerPasses(QQmlSA::PassManager *manager,
820 const QQmlSA::Element &rootElement)
821{
822 const QQmlSA::LoggerWarningId attachedReuseCategory = [manager]() {
823 if (manager->isCategoryEnabled(quickAttachedPropertyReuse))
824 return quickAttachedPropertyReuse;
825 if (manager->isCategoryEnabled(qmlAttachedPropertyReuse))
826 return qmlAttachedPropertyReuse;
827 return quickControlsAttachedPropertyReuse;
828 }();
829
830 const bool hasQuick = manager->hasImportedModule("QtQuick");
831 const bool hasQuickLayouts = manager->hasImportedModule("QtQuick.Layouts");
832 const bool hasQuickControls = manager->hasImportedModule("QtQuick.Templates")
833 || manager->hasImportedModule("QtQuick.Controls")
834 || manager->hasImportedModule("QtQuick.Controls.Basic");
835
836 Q_UNUSED(rootElement);
837
838 if (hasQuick) {
839 manager->registerElementPass(std::make_unique<AnchorsValidatorPass>(manager));
840 manager->registerElementPass(std::make_unique<PropertyChangesValidatorPass>(manager));
841 manager->registerElementPass(std::make_unique<StateNoItemChildrenValidator>(manager));
842 manager->registerPropertyPass(std::make_unique<QQuickLiteralBindingCheck>(manager),
843 QAnyStringView(), QAnyStringView());
844 manager->registerPropertyPass(std::make_unique<ColorValidatorPass>(manager),
845 QAnyStringView(), QAnyStringView());
846
847 auto forbiddenChildProperty =
848 std::make_unique<ForbiddenChildrenPropertyValidatorPass>(manager);
849
850 for (const QString &element : { u"Grid"_s, u"Flow"_s }) {
851 for (const QString &property : { u"anchors"_s, u"x"_s, u"y"_s }) {
852 forbiddenChildProperty->addWarning(
853 "QtQuick", element, property,
854 u"Cannot specify %1 for items inside %2. %2 will not function."_s.arg(
855 property, element));
856 }
857 }
858
859 if (hasQuickLayouts) {
860 forbiddenChildProperty->addWarning(
861 "QtQuick.Layouts", "Layout", "anchors",
862 "Detected anchors on an item that is managed by a layout. This is undefined "
863 u"behavior; use Layout.alignment instead.");
864 forbiddenChildProperty->addWarning(
865 "QtQuick.Layouts", "Layout", "x",
866 "Detected x on an item that is managed by a layout. This is undefined "
867 u"behavior; use Layout.leftMargin or Layout.rightMargin instead.");
868 forbiddenChildProperty->addWarning(
869 "QtQuick.Layouts", "Layout", "y",
870 "Detected y on an item that is managed by a layout. This is undefined "
871 u"behavior; use Layout.topMargin or Layout.bottomMargin instead.");
872 forbiddenChildProperty->addWarning(
873 "QtQuick.Layouts", "Layout", "width",
874 "Detected width on an item that is managed by a layout. This is undefined "
875 u"behavior; use implicitWidth or Layout.preferredWidth instead.");
876 forbiddenChildProperty->addWarning(
877 "QtQuick.Layouts", "Layout", "height",
878 "Detected height on an item that is managed by a layout. This is undefined "
879 u"behavior; use implictHeight or Layout.preferredHeight instead.");
880 }
881
882 manager->registerElementPass(std::move(forbiddenChildProperty));
883 }
884
885 auto attachedPropertyType = std::make_shared<AttachedPropertyTypeValidatorPass>(manager);
886
887 auto addAttachedWarning = [&](TypeDescription attachedType, QList<TypeDescription> allowedTypes,
888 QAnyStringView warning, bool allowInDelegate = false) {
889 QString attachedTypeName = attachedPropertyType->addWarning(attachedType, allowedTypes,
890 allowInDelegate, warning);
891 if (attachedTypeName.isEmpty())
892 return;
893
894 manager->registerPropertyPass(attachedPropertyType, attachedType.module,
895 u"$internal$."_s + attachedTypeName, {}, false);
896 };
897
898 auto addVarBindingWarning =
899 [&](QAnyStringView moduleName, QAnyStringView typeName,
900 const QMultiHash<QString, TypeDescription> &expectedPropertyTypes) {
901 auto varBindingType = std::make_shared<VarBindingTypeValidatorPass>(
902 manager, expectedPropertyTypes);
903 for (const auto &propertyName : expectedPropertyTypes.uniqueKeys()) {
904 manager->registerPropertyPass(varBindingType, moduleName, typeName,
905 propertyName);
906 }
907 };
908
909 if (hasQuick) {
910 addVarBindingWarning("QtQuick", "TableView",
911 { { "columnWidthProvider", { "", "function" } },
912 { "rowHeightProvider", { "", "function" } } });
913 addAttachedWarning({ "QtQuick", "Accessible" },
914 { { "QtQuick", "Item" }, { "QtQuick.Templates", "Action" } },
915 "Accessible attached property must be attached to an object deriving "
916 "from Item or Action");
917 addAttachedWarning({ "QtQuick", "LayoutMirroring" },
918 { { "QtQuick", "Item" }, { "QtQuick", "Window" } },
919 "LayoutMirroring attached property must be attached to an object deriving from Item or Window");
920 addAttachedWarning({ "QtQuick", "EnterKey" }, { { "QtQuick", "Item" } },
921 "EnterKey attached property must be attached to an object deriving from Item");
922 }
923 if (hasQuickLayouts) {
924 addAttachedWarning({ "QtQuick.Layouts", "Layout" }, { { "QtQuick", "Item" } },
925 "Layout attached property must be attached to an object deriving from Item");
926 addAttachedWarning({ "QtQuick.Layouts", "StackLayout" }, { { "QtQuick", "Item" } },
927 "StackLayout attached property must be attached to an object deriving from Item");
928 }
929
930
931 if (hasQuickControls) {
932 manager->registerElementPass(std::make_unique<ControlsSwipeDelegateValidatorPass>(manager));
933 manager->registerPropertyPass(std::make_unique<AttachedPropertyReuse>(
934 manager, attachedReuseCategory), "", "");
935
936 addAttachedWarning({ "QtQuick.Templates", "ScrollBar" },
937 { { "QtQuick", "Flickable" }, { "QtQuick.Templates", "ScrollView" } },
938 "ScrollBar attached property must be attached to an object deriving from Flickable or ScrollView");
939 addAttachedWarning({ "QtQuick.Templates", "ScrollIndicator" },
940 { { "QtQuick", "Flickable" } },
941 "ScrollIndicator attached property must be attached to an object deriving from Flickable");
942 addAttachedWarning({ "QtQuick.Templates", "TextArea" }, { { "QtQuick", "Flickable" } },
943 "TextArea attached property must be attached to an object deriving from Flickable");
944 addAttachedWarning({ "QtQuick.Templates", "SplitView" }, { { "QtQuick", "Item" } },
945 "SplitView attached property must be attached to an object deriving from Item");
946 addAttachedWarning({ "QtQuick.Templates", "StackView" }, { { "QtQuick", "Item" } },
947 "StackView attached property must be attached to an object deriving from Item");
948 addAttachedWarning({ "QtQuick.Templates", "ToolTip" }, { { "QtQuick", "Item" } },
949 "ToolTip attached property must be attached to an object deriving from Item");
950 addAttachedWarning({ "QtQuick.Templates", "SwipeDelegate" }, { { "QtQuick", "Item" } },
951 "SwipeDelegate attached property must be attached to an object deriving from Item");
952 addAttachedWarning({ "QtQuick.Templates", "SwipeView" }, { { "QtQuick", "Item" } },
953 "SwipeView attached property must be attached to an object deriving from Item");
954 addVarBindingWarning("QtQuick.Templates", "Tumbler",
955 { { "contentItem", { "QtQuick", "PathView" } },
956 { "contentItem", { "QtQuick", "ListView" } } });
957 addVarBindingWarning("QtQuick.Templates", "SpinBox",
958 { { "textFromValue", { "", "function" } },
959 { "valueFromText", { "", "function" } } });
960 } else if (attachedReuseCategory != quickControlsAttachedPropertyReuse) {
961 manager->registerPropertyPass(std::make_unique<AttachedPropertyReuse>(
962 manager, attachedReuseCategory), "", "");
963 }
964
965 if (manager->hasImportedModule(u"QtQuick.Controls.macOS"_s)
966 || manager->hasImportedModule(u"QtQuick.Controls.Windows"_s))
967 manager->registerElementPass(std::make_unique<ControlsNativeValidatorPass>(manager));
968}
969
972 , m_propertyChanges(resolveType("QtQuick", "PropertyChanges"))
973{
974}
975
976bool PropertyChangesValidatorPass::shouldRun(const QQmlSA::Element &element)
977{
978 return !m_propertyChanges.isNull() && element.inherits(m_propertyChanges);
979}
980
981void PropertyChangesValidatorPass::run(const QQmlSA::Element &element)
982{
983 const QQmlSA::Binding::Bindings bindings = element.ownPropertyBindings();
984
985 const auto target =
986 std::find_if(bindings.constBegin(), bindings.constEnd(),
987 [](const auto binding) { return binding.propertyName() == u"target"_s; });
988 if (target == bindings.constEnd())
989 return;
990
991 QString targetId = u"<id>"_s;
992 const auto targetLocation = target.value().sourceLocation();
993 const QString targetBinding = sourceCode(targetLocation);
994 const QQmlSA::Element targetElement = resolveIdToElement(targetBinding, element);
995 if (!targetElement.isNull())
996 targetId = targetBinding;
997
998 bool hadCustomParsedBindings = false;
999 for (auto it = bindings.constBegin(); it != bindings.constEnd(); ++it) {
1000 const auto &propertyName = it.key();
1001 const auto &propertyBinding = it.value();
1002 if (element.hasProperty(propertyName))
1003 continue;
1004
1005 const QQmlSA::SourceLocation bindingLocation = propertyBinding.sourceLocation();
1006 if (!targetElement.isNull() && !targetElement.hasProperty(propertyName)) {
1007 emitWarning(
1008 "Unknown property \"%1\" in PropertyChanges."_L1.arg(propertyName),
1009 quickPropertyChangesParsed, bindingLocation);
1010 continue;
1011 }
1012
1013 QString binding = sourceCode(bindingLocation);
1014 if (binding.length() > 16)
1015 binding = binding.left(13) + "..."_L1;
1016
1017 hadCustomParsedBindings = true;
1018 emitWarning("Property \"%1\" is custom-parsed in PropertyChanges. "
1019 "You should phrase this binding as \"%2.%1: %3\""_L1.arg(propertyName, targetId,
1020 binding),
1021 quickPropertyChangesParsed, bindingLocation);
1022 }
1023
1024 if (hadCustomParsedBindings && !targetElement.isNull()) {
1025 emitWarning("You should remove any bindings on the \"target\" property and avoid "
1026 "custom-parsed bindings in PropertyChanges.",
1027 quickPropertyChangesParsed, targetLocation);
1028 }
1029}
1030
1033 , m_state(resolveType("QtQuick", "State"))
1034 , m_anchorChanges(resolveType("QtQuick", "AnchorChanges"))
1035 , m_parentChanges(resolveType("QtQuick", "ParentChange"))
1036 , m_propertyChanges(resolveType("QtQuick", "PropertyChanges"))
1037 , m_stateChangeScript(resolveType("QtQuick", "StateChangeScript"))
1038{}
1039
1040bool StateNoItemChildrenValidator::shouldRun(const QQmlSA::Element &element)
1041{
1042 return element.inherits(m_state);
1043}
1044
1045void StateNoItemChildrenValidator::run(const QQmlSA::Element &element)
1046{
1047 const auto &childScopes = QQmlJSScope::scope(element)->childScopes();
1048 for (const auto &child : childScopes) {
1049 if (child->scopeType() != QQmlSA::ScopeType::QMLScope)
1050 continue;
1051
1052 if (child->inherits(QQmlJSScope::scope(m_anchorChanges))
1053 || child->inherits(QQmlJSScope::scope(m_parentChanges))
1054 || child->inherits(QQmlJSScope::scope(m_propertyChanges))
1055 || child->inherits(QQmlJSScope::scope(m_stateChangeScript))) {
1056 continue;
1057 }
1058 QString msg = "A State cannot have a child item of type %1"_L1.arg(child->baseTypeName());
1059 auto loc = QQmlSA::SourceLocationPrivate::createQQmlSASourceLocation(
1060 child->sourceLocation());
1061 emitWarning(msg, quickStateNoChildItem, loc);
1062 }
1063}
1064
1065QT_END_NAMESPACE
1066
1067#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