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 return QString();
96 }
97
98 for (const TypeDescription &desc : allowedTypes) {
99 const QQmlSA::Element type = resolveType(desc.module, desc.name);
100 if (type.isNull())
101 continue;
102 elements.push_back(type);
103 }
104
105 m_attachedTypes.insert(
106 { std::make_pair<>(attachedType.internalId(),
107 Warning{ elements, allowInDelegate, warning.toString() }) });
108
109 return attachedType.internalId();
110}
111
112void AttachedPropertyTypeValidatorPass::checkWarnings(const QQmlSA::Element &element,
113 const QQmlSA::Element &scopeUsedIn,
114 const QQmlSA::SourceLocation &location)
115{
116 auto warning = m_attachedTypes.constFind(element.internalId());
117 if (warning == m_attachedTypes.cend())
118 return;
119 for (const QQmlSA::Element &type : warning->allowedTypes) {
120 if (scopeUsedIn.inherits(type))
121 return;
122 }
123 // You can use e.g. Layout.leftMargin: 4 in PropertyChanges;
124 // custom parser can do arbitrary things with their contained bindings
125 if ( QQmlJSScope::scope(scopeUsedIn)->isInCustomParserParent() )
126 return;
127
128 if (warning->allowInDelegate) {
129 if (scopeUsedIn.isPropertyRequired(u"index"_s)
130 || scopeUsedIn.isPropertyRequired(u"model"_s))
131 return;
132
133 // If the scope is at the root level, we cannot know whether it will be used
134 // as a delegate or not.
135 if (scopeUsedIn.isFileRootComponent())
136 return;
137
138 for (const QQmlSA::Binding &binding :
139 scopeUsedIn.parentScope().propertyBindings(u"delegate"_s)) {
140 if (!binding.hasObject())
141 continue;
142 if (binding.objectType() == scopeUsedIn)
143 return;
144 }
145 }
146
147 emitWarning(warning->message, quickAttachedPropertyType, location);
148}
149
150void AttachedPropertyTypeValidatorPass::onBinding(const QQmlSA::Element &element,
151 const QString &propertyName,
152 const QQmlSA::Binding &binding,
153 const QQmlSA::Element &bindingScope,
154 const QQmlSA::Element &value)
155{
156 Q_UNUSED(value)
157
158 // We can only analyze simple attached bindings since we don't see
159 // the grouped and attached properties that lead up to this here.
160 //
161 // TODO: This is very crude.
162 // We should add API for grouped and attached properties.
163 if (propertyName.count(QLatin1Char('.')) > 1)
164 return;
165
166 checkWarnings(bindingScope.baseType(), element, binding.sourceLocation());
167}
168
169void AttachedPropertyTypeValidatorPass::onRead(const QQmlSA::Element &element,
170 const QString &propertyName,
171 const QQmlSA::Element &readScope,
172 QQmlSA::SourceLocation location)
173{
174 // If the attachment does not have such a property or method then
175 // it's either a more general error or an enum. Enums are fine.
176 if (element.hasProperty(propertyName) || element.hasMethod(propertyName))
177 checkWarnings(element, readScope, location);
178}
179
180void AttachedPropertyTypeValidatorPass::onWrite(const QQmlSA::Element &element,
181 const QString &propertyName,
182 const QQmlSA::Element &value,
183 const QQmlSA::Element &writeScope,
184 QQmlSA::SourceLocation location)
185{
186 Q_UNUSED(propertyName)
187 Q_UNUSED(value)
188
189 checkWarnings(element, writeScope, location);
190}
191
194{
195 m_elements = {
196 ControlElement { "Control",
197 QStringList { "background", "contentItem", "leftPadding", "rightPadding",
198 "topPadding", "bottomPadding", "horizontalPadding",
199 "verticalPadding", "padding" },
200 false, true },
201 ControlElement { "Button", QStringList { "indicator" } },
202 ControlElement {
203 "ApplicationWindow",
204 QStringList { "background", "contentItem", "header", "footer", "menuBar" } },
205 ControlElement { "ComboBox", QStringList { "indicator" } },
206 ControlElement { "Dial", QStringList { "handle" } },
207 ControlElement { "GroupBox", QStringList { "label" } },
208 ControlElement { "$internal$.QQuickIndicatorButton", QStringList { "indicator" }, false },
209 ControlElement { "Label", QStringList { "background" } },
210 ControlElement { "MenuItem", QStringList { "arrow" } },
211 ControlElement { "Page", QStringList { "header", "footer" } },
212 ControlElement { "Popup", QStringList { "background", "contentItem" } },
213 ControlElement { "RangeSlider", QStringList { "handle" } },
214 ControlElement { "Slider", QStringList { "handle" } },
215 ControlElement { "$internal$.QQuickSwipe",
216 QStringList { "leftItem", "behindItem", "rightItem" }, false },
217 ControlElement { "TextArea", QStringList { "background" } },
218 ControlElement { "TextField", QStringList { "background" } },
219 };
220
221 for (const QString &module : { u"QtQuick.Controls.macOS"_s, u"QtQuick.Controls.Windows"_s }) {
222 if (!manager->hasImportedModule(module))
223 continue;
224
225 QQmlSA::Element control = resolveType(module, "Control");
226
227 for (ControlElement &element : m_elements) {
228 auto type = resolveType(element.isInModuleControls ? module : "QtQuick.Templates",
229 element.name);
230
231 if (type.isNull())
232 continue;
233
234 element.inheritsControl = !element.isControl && type.inherits(control);
235 element.element = type;
236 }
237
238 m_elements.removeIf([](const ControlElement &element) { return element.element.isNull(); });
239
240 break;
241 }
242}
243
244bool ControlsNativeValidatorPass::shouldRun(const QQmlSA::Element &element)
245{
246 for (const ControlElement &controlElement : m_elements) {
247 // If our element inherits control, we don't have to individually check for them here.
248 if (controlElement.inheritsControl)
249 continue;
250 if (element.inherits(controlElement.element))
251 return true;
252 }
253 return false;
254}
255
256void ControlsNativeValidatorPass::run(const QQmlSA::Element &element)
257{
258 for (const ControlElement &controlElement : m_elements) {
259 if (element.inherits(controlElement.element)) {
260 for (const QString &propertyName : controlElement.restrictedProperties) {
261 if (element.hasOwnPropertyBindings(propertyName)) {
262 emitWarning(QStringLiteral("Not allowed to override \"%1\" because native "
263 "styles cannot be customized: See "
264 "https://doc-snapshots.qt.io/qt6-dev/"
265 "qtquickcontrols-customize.html#customization-"
266 "reference for more information.")
267 .arg(propertyName),
268 quickControlsNativeCustomize, element.sourceLocation());
269 }
270 }
271 // Since all the different types we have rules for don't inherit from each other (except
272 // for Control) we don't have to keep checking whether other types match once we've
273 // found one that has been inherited from.
274 if (!controlElement.isControl)
275 break;
276 }
277 }
278}
279
280AnchorsValidatorPass::AnchorsValidatorPass(QQmlSA::PassManager *manager)
282 , m_item(resolveType("QtQuick", "Item"))
283{
284}
285
286bool AnchorsValidatorPass::shouldRun(const QQmlSA::Element &element)
287{
288 return !m_item.isNull() && element.inherits(m_item)
289 && element.hasOwnPropertyBindings(u"anchors"_s);
290}
291
292void AnchorsValidatorPass::run(const QQmlSA::Element &element)
293{
294 enum BindingLocation { Exists = 1, Own = (1 << 1) };
295 QHash<QString, qint8> bindings;
296
297 const QStringList properties = { u"left"_s, u"right"_s, u"horizontalCenter"_s,
298 u"top"_s, u"bottom"_s, u"verticalCenter"_s,
299 u"baseline"_s };
300
301 QList<QQmlSA::Binding> anchorBindings = element.propertyBindings(u"anchors"_s);
302
303 for (qsizetype i = anchorBindings.size() - 1; i >= 0; i--) {
304 auto groupType = anchorBindings[i].groupType();
305 if (groupType.isNull())
306 continue;
307
308 for (const QString &name : properties) {
309
310 const auto &propertyBindings = groupType.ownPropertyBindings(name);
311 if (propertyBindings.begin() == propertyBindings.end())
312 continue;
313
314 bool isUndefined = false;
315 for (const auto &propertyBinding : propertyBindings) {
316 if (propertyBinding.hasUndefinedScriptValue()) {
317 isUndefined = true;
318 break;
319 }
320 }
321
322 if (isUndefined)
323 bindings[name] = 0;
324 else
325 bindings[name] |= Exists | ((i == 0) ? Own : 0);
326 }
327 }
328
329 auto ownSourceLocation = [&](QStringList properties) -> QQmlSA::SourceLocation {
330 QQmlSA::SourceLocation warnLoc;
331
332 for (const QString &name : properties) {
333 if (bindings[name] & Own) {
334 QQmlSA::Element groupType = QQmlSA::Element{ anchorBindings[0].groupType() };
335 auto bindings = groupType.ownPropertyBindings(name);
336 Q_ASSERT(bindings.begin() != bindings.end());
337 warnLoc = bindings.begin().value().sourceLocation();
338 break;
339 }
340 }
341 return warnLoc;
342 };
343
344 if ((bindings[u"left"_s] & bindings[u"right"_s] & bindings[u"horizontalCenter"_s]) & Exists) {
345 QQmlSA::SourceLocation warnLoc =
346 ownSourceLocation({ u"left"_s, u"right"_s, u"horizontalCenter"_s });
347
348 if (warnLoc.isValid()) {
349 emitWarning(
350 "Cannot specify left, right, and horizontalCenter anchors at the same time.",
351 quickAnchorCombinations, warnLoc);
352 }
353 }
354
355 if ((bindings[u"top"_s] & bindings[u"bottom"_s] & bindings[u"verticalCenter"_s]) & Exists) {
356 QQmlSA::SourceLocation warnLoc =
357 ownSourceLocation({ u"top"_s, u"bottom"_s, u"verticalCenter"_s });
358 if (warnLoc.isValid()) {
359 emitWarning("Cannot specify top, bottom, and verticalCenter anchors at the same time.",
360 quickAnchorCombinations, warnLoc);
361 }
362 }
363
364 if ((bindings[u"baseline"_s] & (bindings[u"bottom"_s] | bindings[u"verticalCenter"_s]))
365 & Exists) {
366 QQmlSA::SourceLocation warnLoc =
367 ownSourceLocation({ u"baseline"_s, u"bottom"_s, u"verticalCenter"_s });
368 if (warnLoc.isValid()) {
369 emitWarning("Baseline anchor cannot be used in conjunction with top, bottom, or "
370 "verticalCenter anchors.",
371 quickAnchorCombinations, warnLoc);
372 }
373 }
374}
375
378 , m_swipeDelegate(resolveType("QtQuick.Controls", "SwipeDelegate"))
379{
380}
381
382bool ControlsSwipeDelegateValidatorPass::shouldRun(const QQmlSA::Element &element)
383{
384 return !m_swipeDelegate.isNull() && element.inherits(m_swipeDelegate);
385}
386
387void ControlsSwipeDelegateValidatorPass::run(const QQmlSA::Element &element)
388{
389 for (const auto &property : { u"background"_s, u"contentItem"_s }) {
390 for (const auto &binding : element.ownPropertyBindings(property)) {
391 if (!binding.hasObject())
392 continue;
393 const QQmlSA::Element element = QQmlSA::Element{ binding.objectType() };
394 const auto &bindings = element.propertyBindings(u"anchors"_s);
395 if (bindings.isEmpty())
396 continue;
397
398 if (bindings.first().bindingType() != QQmlSA::BindingType::GroupProperty)
399 continue;
400
401 auto anchors = bindings.first().groupType();
402 for (const auto &disallowed : { u"fill"_s, u"centerIn"_s, u"left"_s, u"right"_s }) {
403 if (anchors.hasPropertyBindings(disallowed)) {
404 QQmlSA::SourceLocation location;
405 const auto &ownBindings = anchors.ownPropertyBindings(disallowed);
406 if (ownBindings.begin() != ownBindings.end()) {
407 location = ownBindings.begin().value().sourceLocation();
408 }
409
410 emitWarning(
411 u"SwipeDelegate: Cannot use horizontal anchors with %1; unable to layout the item."_s
412 .arg(property),
413 quickAnchorCombinations, location);
414 break;
415 }
416 }
417 break;
418 }
419 }
420
421 const auto &swipe = element.ownPropertyBindings(u"swipe"_s);
422 if (swipe.begin() == swipe.end())
423 return;
424
425 const auto firstSwipe = swipe.begin().value();
426 if (firstSwipe.bindingType() != QQmlSA::BindingType::GroupProperty)
427 return;
428
429 auto group = firstSwipe.groupType();
430
431 const std::array ownDirBindings = { group.ownPropertyBindings(u"right"_s),
432 group.ownPropertyBindings(u"left"_s),
433 group.ownPropertyBindings(u"behind"_s) };
434
435 auto ownBindingIterator =
436 std::find_if(ownDirBindings.begin(), ownDirBindings.end(),
437 [](const auto &bindings) { return bindings.begin() != bindings.end(); });
438
439 if (ownBindingIterator == ownDirBindings.end())
440 return;
441
442 if (group.hasPropertyBindings(u"behind"_s)
443 && (group.hasPropertyBindings(u"right"_s) || group.hasPropertyBindings(u"left"_s))) {
444 emitWarning("SwipeDelegate: Cannot set both behind and left/right properties",
445 quickAnchorCombinations, ownBindingIterator->begin().value().sourceLocation());
446 }
447}
448
450 QQmlSA::PassManager *manager,
451 const QMultiHash<QString, TypeDescription> &expectedPropertyTypes)
453{
454 QMultiHash<QString, QQmlSA::Element> propertyTypes;
455
456 for (const auto &pair : expectedPropertyTypes.asKeyValueRange()) {
457 const QQmlSA::Element propType = pair.second.module.isEmpty()
458 ? resolveBuiltinType(pair.second.name)
459 : resolveType(pair.second.module, pair.second.name);
460 if (!propType.isNull())
461 propertyTypes.insert(pair.first, propType);
462 }
463
464 m_expectedPropertyTypes = propertyTypes;
465}
466
467void VarBindingTypeValidatorPass::onBinding(const QQmlSA::Element &element,
468 const QString &propertyName,
469 const QQmlSA::Binding &binding,
470 const QQmlSA::Element &bindingScope,
471 const QQmlSA::Element &value)
472{
473 Q_UNUSED(element);
474 Q_UNUSED(bindingScope);
475
476 const auto range = m_expectedPropertyTypes.equal_range(propertyName);
477
478 if (range.first == range.second)
479 return;
480
481 QQmlSA::Element bindingType;
482
483 if (!value.isNull()) {
484 bindingType = value;
485 } else {
486 if (QQmlSA::Binding::isLiteralBinding(binding.bindingType())) {
487 bindingType = resolveLiteralType(binding);
488 } else {
489 switch (binding.bindingType()) {
490 case QQmlSA::BindingType::Object:
491 bindingType = QQmlSA::Element{ binding.objectType() };
492 break;
493 case QQmlSA::BindingType::Script:
494 break;
495 default:
496 return;
497 }
498 }
499 }
500
501 if (std::find_if(range.first, range.second,
502 [&](const QQmlSA::Element &scope) { return bindingType.inherits(scope); })
503 == range.second) {
504
505 const bool bindingTypeIsComposite = bindingType.isComposite();
506 if (bindingTypeIsComposite && !bindingType.baseType()) {
507 /* broken module or missing import, there is nothing we
508 can really check here, as something is amiss. We
509 simply skip this binding, and assume that whatever
510 caused the breakage here will already cause another
511 warning somewhere else.
512 */
513 return;
514 }
515 const QString bindingTypeName =
516 bindingTypeIsComposite ? bindingType.baseType().name()
517 : bindingType.name();
518 QStringList expectedTypeNames;
519
520 for (auto it = range.first; it != range.second; it++)
521 expectedTypeNames << it.value().name();
522
523 emitWarning(u"Unexpected type for property \"%1\" expected %2 got %3"_s.arg(
524 propertyName, expectedTypeNames.join(u", "_s), bindingTypeName),
525 quickUnexpectedVarType, binding.sourceLocation());
526 }
527}
528
530{
531public:
532 ColorValidatorPass(QQmlSA::PassManager *manager);
533
534 void onBinding(const QQmlSA::Element &element, const QString &propertyName,
535 const QQmlSA::Binding &binding, const QQmlSA::Element &bindingScope,
536 const QQmlSA::Element &value) override;
537private:
538 QQmlSA::Element m_colorType;
539 // we support both long and short hex codes for both RGB and ARGB,
540 // so 3, 4, 6 and 8 hex digits are allowed
541 static inline const QRegularExpression s_hexPattern{ "^#((([0-9A-Fa-f]{3}){1,2})|(([0-9A-Fa-f]{4}){1,2}))$"_L1 };
542 // list taken from https://doc.qt.io/qt-6/qcolor.html#fromString
543 QStringList m_colorNames = {
544 u"aliceblue"_s,
545 u"antiquewhite"_s,
546 u"aqua"_s,
547 u"aquamarine"_s,
548 u"azure"_s,
549 u"beige"_s,
550 u"bisque"_s,
551 u"black"_s,
552 u"blanchedalmond"_s,
553 u"blue"_s,
554 u"blueviolet"_s,
555 u"brown"_s,
556 u"burlywood"_s,
557 u"cadetblue"_s,
558 u"chartreuse"_s,
559 u"chocolate"_s,
560 u"coral"_s,
561 u"cornflowerblue"_s,
562 u"cornsilk"_s,
563 u"crimson"_s,
564 u"cyan"_s,
565 u"darkblue"_s,
566 u"darkcyan"_s,
567 u"darkgoldenrod"_s,
568 u"darkgray"_s,
569 u"darkgreen"_s,
570 u"darkgrey"_s,
571 u"darkkhaki"_s,
572 u"darkmagenta"_s,
573 u"darkolivegreen"_s,
574 u"darkorange"_s,
575 u"darkorchid"_s,
576 u"darkred"_s,
577 u"darksalmon"_s,
578 u"darkseagreen"_s,
579 u"darkslateblue"_s,
580 u"darkslategray"_s,
581 u"darkslategrey"_s,
582 u"darkturquoise"_s,
583 u"darkviolet"_s,
584 u"deeppink"_s,
585 u"deepskyblue"_s,
586 u"dimgray"_s,
587 u"dimgrey"_s,
588 u"dodgerblue"_s,
589 u"firebrick"_s,
590 u"floralwhite"_s,
591 u"forestgreen"_s,
592 u"fuchsia"_s,
593 u"gainsboro"_s,
594 u"ghostwhite"_s,
595 u"gold"_s,
596 u"goldenrod"_s,
597 u"gray"_s,
598 u"green"_s,
599 u"greenyellow"_s,
600 u"grey"_s,
601 u"honeydew"_s,
602 u"hotpink"_s,
603 u"indianred"_s,
604 u"indigo"_s,
605 u"ivory"_s,
606 u"khaki"_s,
607 u"lavender"_s,
608 u"lavenderblush"_s,
609 u"lawngreen"_s,
610 u"lemonchiffon"_s,
611 u"lightblue"_s,
612 u"lightcoral"_s,
613 u"lightcyan"_s,
614 u"lightgoldenrodyellow"_s,
615 u"lightgray"_s,
616 u"lightgreen"_s,
617 u"lightgrey"_s,
618 u"lightpink"_s,
619 u"lightsalmon"_s,
620 u"lightseagreen"_s,
621 u"lightskyblue"_s,
622 u"lightslategray"_s,
623 u"lightslategrey"_s,
624 u"lightsteelblue"_s,
625 u"lightyellow"_s,
626 u"lime"_s,
627 u"limegreen"_s,
628 u"linen"_s,
629 u"magenta"_s,
630 u"maroon"_s,
631 u"mediumaquamarine"_s,
632 u"mediumblue"_s,
633 u"mediumorchid"_s,
634 u"mediumpurple"_s,
635 u"mediumseagreen"_s,
636 u"mediumslateblue"_s,
637 u"mediumspringgreen"_s,
638 u"mediumturquoise"_s,
639 u"mediumvioletred"_s,
640 u"midnightblue"_s,
641 u"mintcream"_s,
642 u"mistyrose"_s,
643 u"moccasin"_s,
644 u"navajowhite"_s,
645 u"navy"_s,
646 u"oldlace"_s,
647 u"olive"_s,
648 u"olivedrab"_s,
649 u"orange"_s,
650 u"orangered"_s,
651 u"orchid"_s,
652 u"palegoldenrod"_s,
653 u"palegreen"_s,
654 u"paleturquoise"_s,
655 u"palevioletred"_s,
656 u"papayawhip"_s,
657 u"peachpuff"_s,
658 u"peru"_s,
659 u"pink"_s,
660 u"plum"_s,
661 u"powderblue"_s,
662 u"purple"_s,
663 u"red"_s,
664 u"rosybrown"_s,
665 u"royalblue"_s,
666 u"saddlebrown"_s,
667 u"salmon"_s,
668 u"sandybrown"_s,
669 u"seagreen"_s,
670 u"seashell"_s,
671 u"sienna"_s,
672 u"silver"_s,
673 u"skyblue"_s,
674 u"slateblue"_s,
675 u"slategray"_s,
676 u"slategrey"_s,
677 u"snow"_s,
678 u"springgreen"_s,
679 u"steelblue"_s,
680 u"tan"_s,
681 u"teal"_s,
682 u"thistle"_s,
683 u"tomato"_s,
684 u"turquoise"_s,
685 u"violet"_s,
686 u"wheat"_s,
687 u"white"_s,
688 u"whitesmoke"_s,
689 u"yellow"_s,
690 u"yellowgreen"_s,
691 };
692};
693
694
695ColorValidatorPass::ColorValidatorPass(QQmlSA::PassManager *manager)
696 : PropertyPass(manager), m_colorType(resolveType("QtQuick"_L1, "color"_L1))
697{
698 Q_ASSERT_X(std::is_sorted(m_colorNames.cbegin(), m_colorNames.cend()), "ColorValidatorPass",
699 "m_colorNames should be sorted!");
700}
701
702void ColorValidatorPass::onBinding(const QQmlSA::Element &element, const QString &propertyName,
703 const QQmlSA::Binding &binding, const QQmlSA::Element &,
704 const QQmlSA::Element &)
705{
706 if (binding.bindingType() != QQmlSA::BindingType::StringLiteral)
707 return;
708 const auto propertyType = element.property(propertyName).type();
709 if (!propertyType || propertyType != m_colorType)
710 return;
711
712 QString colorName = binding.stringValue();
713 // for "named" colors, QColor::fromString does not care about
714 // the case
715 if (!colorName.startsWith(u'#'))
716 colorName = std::move(colorName).toLower();
717 if (s_hexPattern.match(colorName).hasMatch())
718 return;
719
720 if (std::binary_search(m_colorNames.cbegin(), m_colorNames.cend(), colorName))
721 return;
722
723 if (colorName == u"transparent")
724 return;
725
726 auto suggestion = QQmlJSUtils::didYouMean(
727 colorName, m_colorNames,
728 QQmlSA::SourceLocationPrivate::sourceLocation(binding.sourceLocation()));
729
730 emitWarningWithOptionalFix(*this, "Invalid color \"%1\"."_L1.arg(colorName), quickColor,
731 binding.sourceLocation(), suggestion);
732}
733
734void AttachedPropertyReuse::onRead(const QQmlSA::Element &element, const QString &propertyName,
735 const QQmlSA::Element &readScope,
736 QQmlSA::SourceLocation location)
737{
738 const auto range = usedAttachedTypes.equal_range(readScope);
739 const auto attachedTypeAndLocation = std::find_if(
740 range.first, range.second, [&](const ElementAndLocation &elementAndLocation) {
741 return elementAndLocation.element == element;
742 });
743 if (attachedTypeAndLocation != range.second) {
744 const QQmlSA::SourceLocation attachedLocation = attachedTypeAndLocation->location;
745
746 // Ignore enum accesses, as these will not cause the attached object to be created.
747 // Also ignore anything we cannot determine.
748 if (!element.hasProperty(propertyName) && !element.hasMethod(propertyName))
749 return;
750
751 for (QQmlSA::Element scope = readScope.parentScope(); !scope.isNull();
752 scope = scope.parentScope()) {
753 const auto range = usedAttachedTypes.equal_range(scope);
754 bool found = false;
755 for (auto it = range.first; it != range.second; ++it) {
756 if (it->element == element) {
757 found = true;
758 break;
759 }
760 }
761 if (!found)
762 continue;
763
764 const QString id = resolveElementToId(scope, readScope);
765 const QQmlSA::SourceLocation idInsertLocation{ attachedLocation.offset(), 0,
766 attachedLocation.startLine(),
767 attachedLocation.startColumn() };
768 QString m = "Reference it by id instead%1:"_L1;
769 m = m.arg(id.isEmpty() ? " (You first have to give the element and id)"_L1 : ""_L1);
770 QQmlSA::FixSuggestion suggestion{ m, idInsertLocation,
771 id.isEmpty() ? u"<id>."_s : (id + '.'_L1) };
772
773 if (!id.isEmpty())
774 suggestion.setAutoApplicable();
775
776 emitWarning("Using attached type %1 already initialized in a parent scope."_L1.arg(
777 element.name()),
778 category, attachedLocation, suggestion);
779 return;
780 }
781
782 return;
783 }
784
785 if (element.hasProperty(propertyName))
786 return; // an actual property
787
788 QQmlSA::Element type = resolveTypeInFileScope(propertyName);
789 QQmlSA::Element attached = resolveAttachedInFileScope(propertyName);
790 if (!type || !attached)
791 return;
792
793 if (category == quickControlsAttachedPropertyReuse) {
794 for (QQmlSA::Element parent = attached; parent; parent = parent.baseType()) {
795 // ### TODO: Make it possible to resolve QQuickAttachedPropertyPropagator
796 // so that we don't have to compare the internal id
797 if (parent.internalId() == "QQuickAttachedPropertyPropagator"_L1) {
798 usedAttachedTypes.insert(readScope, {attached, location});
799 break;
800 }
801 }
802
803 } else {
804 usedAttachedTypes.insert(readScope, {attached, location});
805 }
806}
807
808void AttachedPropertyReuse::onWrite(const QQmlSA::Element &element, const QString &propertyName,
809 const QQmlSA::Element &value, const QQmlSA::Element &writeScope,
810 QQmlSA::SourceLocation location)
811{
812 Q_UNUSED(value);
813 onRead(element, propertyName, writeScope, location);
814}
815
816void QmlLintQuickPlugin::registerPasses(QQmlSA::PassManager *manager,
817 const QQmlSA::Element &rootElement)
818{
819 const QQmlSA::LoggerWarningId attachedReuseCategory = [manager]() {
820 if (manager->isCategoryEnabled(quickAttachedPropertyReuse))
821 return quickAttachedPropertyReuse;
822 if (manager->isCategoryEnabled(qmlAttachedPropertyReuse))
823 return qmlAttachedPropertyReuse;
824 return quickControlsAttachedPropertyReuse;
825 }();
826
827 const bool hasQuick = manager->hasImportedModule("QtQuick");
828 const bool hasQuickLayouts = manager->hasImportedModule("QtQuick.Layouts");
829 const bool hasQuickControls = manager->hasImportedModule("QtQuick.Templates")
830 || manager->hasImportedModule("QtQuick.Controls")
831 || manager->hasImportedModule("QtQuick.Controls.Basic");
832
833 Q_UNUSED(rootElement);
834
835 if (hasQuick) {
836 manager->registerElementPass(std::make_unique<AnchorsValidatorPass>(manager));
837 manager->registerElementPass(std::make_unique<PropertyChangesValidatorPass>(manager));
838 manager->registerElementPass(std::make_unique<StateNoItemChildrenValidator>(manager));
839 manager->registerPropertyPass(std::make_unique<QQuickLiteralBindingCheck>(manager),
840 QAnyStringView(), QAnyStringView());
841 manager->registerPropertyPass(std::make_unique<ColorValidatorPass>(manager),
842 QAnyStringView(), QAnyStringView());
843
844 auto forbiddenChildProperty =
845 std::make_unique<ForbiddenChildrenPropertyValidatorPass>(manager);
846
847 for (const QString &element : { u"Grid"_s, u"Flow"_s }) {
848 for (const QString &property : { u"anchors"_s, u"x"_s, u"y"_s }) {
849 forbiddenChildProperty->addWarning(
850 "QtQuick", element, property,
851 u"Cannot specify %1 for items inside %2. %2 will not function."_s.arg(
852 property, element));
853 }
854 }
855
856 if (hasQuickLayouts) {
857 forbiddenChildProperty->addWarning(
858 "QtQuick.Layouts", "Layout", "anchors",
859 "Detected anchors on an item that is managed by a layout. This is undefined "
860 u"behavior; use Layout.alignment instead.");
861 forbiddenChildProperty->addWarning(
862 "QtQuick.Layouts", "Layout", "x",
863 "Detected x on an item that is managed by a layout. This is undefined "
864 u"behavior; use Layout.leftMargin or Layout.rightMargin instead.");
865 forbiddenChildProperty->addWarning(
866 "QtQuick.Layouts", "Layout", "y",
867 "Detected y on an item that is managed by a layout. This is undefined "
868 u"behavior; use Layout.topMargin or Layout.bottomMargin instead.");
869 forbiddenChildProperty->addWarning(
870 "QtQuick.Layouts", "Layout", "width",
871 "Detected width on an item that is managed by a layout. This is undefined "
872 u"behavior; use implicitWidth or Layout.preferredWidth instead.");
873 forbiddenChildProperty->addWarning(
874 "QtQuick.Layouts", "Layout", "height",
875 "Detected height on an item that is managed by a layout. This is undefined "
876 u"behavior; use implictHeight or Layout.preferredHeight instead.");
877 }
878
879 manager->registerElementPass(std::move(forbiddenChildProperty));
880 }
881
882 auto attachedPropertyType = std::make_shared<AttachedPropertyTypeValidatorPass>(manager);
883
884 auto addAttachedWarning = [&](TypeDescription attachedType, QList<TypeDescription> allowedTypes,
885 QAnyStringView warning, bool allowInDelegate = false) {
886 QString attachedTypeName = attachedPropertyType->addWarning(attachedType, allowedTypes,
887 allowInDelegate, warning);
888 if (attachedTypeName.isEmpty())
889 return;
890
891 manager->registerPropertyPass(attachedPropertyType, attachedType.module,
892 u"$internal$."_s + attachedTypeName, {}, false);
893 };
894
895 auto addVarBindingWarning =
896 [&](QAnyStringView moduleName, QAnyStringView typeName,
897 const QMultiHash<QString, TypeDescription> &expectedPropertyTypes) {
898 auto varBindingType = std::make_shared<VarBindingTypeValidatorPass>(
899 manager, expectedPropertyTypes);
900 for (const auto &propertyName : expectedPropertyTypes.uniqueKeys()) {
901 manager->registerPropertyPass(varBindingType, moduleName, typeName,
902 propertyName);
903 }
904 };
905
906 if (hasQuick) {
907 addVarBindingWarning("QtQuick", "TableView",
908 { { "columnWidthProvider", { "", "function" } },
909 { "rowHeightProvider", { "", "function" } } });
910 addAttachedWarning({ "QtQuick", "Accessible" },
911 { { "QtQuick", "Item" }, { "QtQuick.Templates", "Action" } },
912 "Accessible attached property must be attached to an object deriving "
913 "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.
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