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 element.inherits(m_item) && element.hasOwnPropertyBindings(u"anchors"_s);
289}
290
291void AnchorsValidatorPass::run(const QQmlSA::Element &element)
292{
293 enum BindingLocation { Exists = 1, Own = (1 << 1) };
294 QHash<QString, qint8> bindings;
295
296 const QStringList properties = { u"left"_s, u"right"_s, u"horizontalCenter"_s,
297 u"top"_s, u"bottom"_s, u"verticalCenter"_s,
298 u"baseline"_s };
299
300 QList<QQmlSA::Binding> anchorBindings = element.propertyBindings(u"anchors"_s);
301
302 for (qsizetype i = anchorBindings.size() - 1; i >= 0; i--) {
303 auto groupType = anchorBindings[i].groupType();
304 if (groupType.isNull())
305 continue;
306
307 for (const QString &name : properties) {
308
309 const auto &propertyBindings = groupType.ownPropertyBindings(name);
310 if (propertyBindings.begin() == propertyBindings.end())
311 continue;
312
313 bool isUndefined = false;
314 for (const auto &propertyBinding : propertyBindings) {
315 if (propertyBinding.hasUndefinedScriptValue()) {
316 isUndefined = true;
317 break;
318 }
319 }
320
321 if (isUndefined)
322 bindings[name] = 0;
323 else
324 bindings[name] |= Exists | ((i == 0) ? Own : 0);
325 }
326 }
327
328 auto ownSourceLocation = [&](QStringList properties) -> QQmlSA::SourceLocation {
329 QQmlSA::SourceLocation warnLoc;
330
331 for (const QString &name : properties) {
332 if (bindings[name] & Own) {
333 QQmlSA::Element groupType = QQmlSA::Element{ anchorBindings[0].groupType() };
334 auto bindings = groupType.ownPropertyBindings(name);
335 Q_ASSERT(bindings.begin() != bindings.end());
336 warnLoc = bindings.begin().value().sourceLocation();
337 break;
338 }
339 }
340 return warnLoc;
341 };
342
343 if ((bindings[u"left"_s] & bindings[u"right"_s] & bindings[u"horizontalCenter"_s]) & Exists) {
344 QQmlSA::SourceLocation warnLoc =
345 ownSourceLocation({ u"left"_s, u"right"_s, u"horizontalCenter"_s });
346
347 if (warnLoc.isValid()) {
348 emitWarning(
349 "Cannot specify left, right, and horizontalCenter anchors at the same time.",
350 quickAnchorCombinations, warnLoc);
351 }
352 }
353
354 if ((bindings[u"top"_s] & bindings[u"bottom"_s] & bindings[u"verticalCenter"_s]) & Exists) {
355 QQmlSA::SourceLocation warnLoc =
356 ownSourceLocation({ u"top"_s, u"bottom"_s, u"verticalCenter"_s });
357 if (warnLoc.isValid()) {
358 emitWarning("Cannot specify top, bottom, and verticalCenter anchors at the same time.",
359 quickAnchorCombinations, warnLoc);
360 }
361 }
362
363 if ((bindings[u"baseline"_s] & (bindings[u"bottom"_s] | bindings[u"verticalCenter"_s]))
364 & Exists) {
365 QQmlSA::SourceLocation warnLoc =
366 ownSourceLocation({ u"baseline"_s, u"bottom"_s, u"verticalCenter"_s });
367 if (warnLoc.isValid()) {
368 emitWarning("Baseline anchor cannot be used in conjunction with top, bottom, or "
369 "verticalCenter anchors.",
370 quickAnchorCombinations, warnLoc);
371 }
372 }
373}
374
377 , m_swipeDelegate(resolveType("QtQuick.Controls", "SwipeDelegate"))
378{
379}
380
381bool ControlsSwipeDelegateValidatorPass::shouldRun(const QQmlSA::Element &element)
382{
383 return element.inherits(m_swipeDelegate);
384}
385
386void ControlsSwipeDelegateValidatorPass::run(const QQmlSA::Element &element)
387{
388 for (const auto &property : { u"background"_s, u"contentItem"_s }) {
389 for (const auto &binding : element.ownPropertyBindings(property)) {
390 if (!binding.hasObject())
391 continue;
392 const QQmlSA::Element element = QQmlSA::Element{ binding.objectType() };
393 const auto &bindings = element.propertyBindings(u"anchors"_s);
394 if (bindings.isEmpty())
395 continue;
396
397 if (bindings.first().bindingType() != QQmlSA::BindingType::GroupProperty)
398 continue;
399
400 auto anchors = bindings.first().groupType();
401 for (const auto &disallowed : { u"fill"_s, u"centerIn"_s, u"left"_s, u"right"_s }) {
402 if (anchors.hasPropertyBindings(disallowed)) {
403 QQmlSA::SourceLocation location;
404 const auto &ownBindings = anchors.ownPropertyBindings(disallowed);
405 if (ownBindings.begin() != ownBindings.end()) {
406 location = ownBindings.begin().value().sourceLocation();
407 }
408
409 emitWarning(
410 u"SwipeDelegate: Cannot use horizontal anchors with %1; unable to layout the item."_s
411 .arg(property),
412 quickAnchorCombinations, location);
413 break;
414 }
415 }
416 break;
417 }
418 }
419
420 const auto &swipe = element.ownPropertyBindings(u"swipe"_s);
421 if (swipe.begin() == swipe.end())
422 return;
423
424 const auto firstSwipe = swipe.begin().value();
425 if (firstSwipe.bindingType() != QQmlSA::BindingType::GroupProperty)
426 return;
427
428 auto group = firstSwipe.groupType();
429
430 const std::array ownDirBindings = { group.ownPropertyBindings(u"right"_s),
431 group.ownPropertyBindings(u"left"_s),
432 group.ownPropertyBindings(u"behind"_s) };
433
434 auto ownBindingIterator =
435 std::find_if(ownDirBindings.begin(), ownDirBindings.end(),
436 [](const auto &bindings) { return bindings.begin() != bindings.end(); });
437
438 if (ownBindingIterator == ownDirBindings.end())
439 return;
440
441 if (group.hasPropertyBindings(u"behind"_s)
442 && (group.hasPropertyBindings(u"right"_s) || group.hasPropertyBindings(u"left"_s))) {
443 emitWarning("SwipeDelegate: Cannot set both behind and left/right properties",
444 quickAnchorCombinations, ownBindingIterator->begin().value().sourceLocation());
445 }
446}
447
449 QQmlSA::PassManager *manager,
450 const QMultiHash<QString, TypeDescription> &expectedPropertyTypes)
452{
453 QMultiHash<QString, QQmlSA::Element> propertyTypes;
454
455 for (const auto &pair : expectedPropertyTypes.asKeyValueRange()) {
456 const QQmlSA::Element propType = pair.second.module.isEmpty()
457 ? resolveBuiltinType(pair.second.name)
458 : resolveType(pair.second.module, pair.second.name);
459 if (!propType.isNull())
460 propertyTypes.insert(pair.first, propType);
461 }
462
463 m_expectedPropertyTypes = propertyTypes;
464}
465
466void VarBindingTypeValidatorPass::onBinding(const QQmlSA::Element &element,
467 const QString &propertyName,
468 const QQmlSA::Binding &binding,
469 const QQmlSA::Element &bindingScope,
470 const QQmlSA::Element &value)
471{
472 Q_UNUSED(element);
473 Q_UNUSED(bindingScope);
474
475 const auto range = m_expectedPropertyTypes.equal_range(propertyName);
476
477 if (range.first == range.second)
478 return;
479
480 QQmlSA::Element bindingType;
481
482 if (!value.isNull()) {
483 bindingType = value;
484 } else {
485 if (QQmlSA::Binding::isLiteralBinding(binding.bindingType())) {
486 bindingType = resolveLiteralType(binding);
487 } else {
488 switch (binding.bindingType()) {
489 case QQmlSA::BindingType::Object:
490 bindingType = QQmlSA::Element{ binding.objectType() };
491 break;
492 case QQmlSA::BindingType::Script:
493 break;
494 default:
495 return;
496 }
497 }
498 }
499
500 if (std::find_if(range.first, range.second,
501 [&](const QQmlSA::Element &scope) { return bindingType.inherits(scope); })
502 == range.second) {
503
504 const bool bindingTypeIsComposite = bindingType.isComposite();
505 if (bindingTypeIsComposite && !bindingType.baseType()) {
506 /* broken module or missing import, there is nothing we
507 can really check here, as something is amiss. We
508 simply skip this binding, and assume that whatever
509 caused the breakage here will already cause another
510 warning somewhere else.
511 */
512 return;
513 }
514 const QString bindingTypeName =
515 bindingTypeIsComposite ? bindingType.baseType().name()
516 : bindingType.name();
517 QStringList expectedTypeNames;
518
519 for (auto it = range.first; it != range.second; it++)
520 expectedTypeNames << it.value().name();
521
522 emitWarning(u"Unexpected type for property \"%1\" expected %2 got %3"_s.arg(
523 propertyName, expectedTypeNames.join(u", "_s), bindingTypeName),
524 quickUnexpectedVarType, binding.sourceLocation());
525 }
526}
527
529{
530public:
531 ColorValidatorPass(QQmlSA::PassManager *manager);
532
533 void onBinding(const QQmlSA::Element &element, const QString &propertyName,
534 const QQmlSA::Binding &binding, const QQmlSA::Element &bindingScope,
535 const QQmlSA::Element &value) override;
536private:
537 QQmlSA::Element m_colorType;
538 // we support both long and short hex codes for both RGB and ARGB,
539 // so 3, 4, 6 and 8 hex digits are allowed
540 static inline const QRegularExpression s_hexPattern{ "^#((([0-9A-Fa-f]{3}){1,2})|(([0-9A-Fa-f]{4}){1,2}))$"_L1 };
541 // list taken from https://doc.qt.io/qt-6/qcolor.html#fromString
542 QStringList m_colorNames = {
543 u"aliceblue"_s,
544 u"antiquewhite"_s,
545 u"aqua"_s,
546 u"aquamarine"_s,
547 u"azure"_s,
548 u"beige"_s,
549 u"bisque"_s,
550 u"black"_s,
551 u"blanchedalmond"_s,
552 u"blue"_s,
553 u"blueviolet"_s,
554 u"brown"_s,
555 u"burlywood"_s,
556 u"cadetblue"_s,
557 u"chartreuse"_s,
558 u"chocolate"_s,
559 u"coral"_s,
560 u"cornflowerblue"_s,
561 u"cornsilk"_s,
562 u"crimson"_s,
563 u"cyan"_s,
564 u"darkblue"_s,
565 u"darkcyan"_s,
566 u"darkgoldenrod"_s,
567 u"darkgray"_s,
568 u"darkgreen"_s,
569 u"darkgrey"_s,
570 u"darkkhaki"_s,
571 u"darkmagenta"_s,
572 u"darkolivegreen"_s,
573 u"darkorange"_s,
574 u"darkorchid"_s,
575 u"darkred"_s,
576 u"darksalmon"_s,
577 u"darkseagreen"_s,
578 u"darkslateblue"_s,
579 u"darkslategray"_s,
580 u"darkslategrey"_s,
581 u"darkturquoise"_s,
582 u"darkviolet"_s,
583 u"deeppink"_s,
584 u"deepskyblue"_s,
585 u"dimgray"_s,
586 u"dimgrey"_s,
587 u"dodgerblue"_s,
588 u"firebrick"_s,
589 u"floralwhite"_s,
590 u"forestgreen"_s,
591 u"fuchsia"_s,
592 u"gainsboro"_s,
593 u"ghostwhite"_s,
594 u"gold"_s,
595 u"goldenrod"_s,
596 u"gray"_s,
597 u"green"_s,
598 u"greenyellow"_s,
599 u"grey"_s,
600 u"honeydew"_s,
601 u"hotpink"_s,
602 u"indianred"_s,
603 u"indigo"_s,
604 u"ivory"_s,
605 u"khaki"_s,
606 u"lavender"_s,
607 u"lavenderblush"_s,
608 u"lawngreen"_s,
609 u"lemonchiffon"_s,
610 u"lightblue"_s,
611 u"lightcoral"_s,
612 u"lightcyan"_s,
613 u"lightgoldenrodyellow"_s,
614 u"lightgray"_s,
615 u"lightgreen"_s,
616 u"lightgrey"_s,
617 u"lightpink"_s,
618 u"lightsalmon"_s,
619 u"lightseagreen"_s,
620 u"lightskyblue"_s,
621 u"lightslategray"_s,
622 u"lightslategrey"_s,
623 u"lightsteelblue"_s,
624 u"lightyellow"_s,
625 u"lime"_s,
626 u"limegreen"_s,
627 u"linen"_s,
628 u"magenta"_s,
629 u"maroon"_s,
630 u"mediumaquamarine"_s,
631 u"mediumblue"_s,
632 u"mediumorchid"_s,
633 u"mediumpurple"_s,
634 u"mediumseagreen"_s,
635 u"mediumslateblue"_s,
636 u"mediumspringgreen"_s,
637 u"mediumturquoise"_s,
638 u"mediumvioletred"_s,
639 u"midnightblue"_s,
640 u"mintcream"_s,
641 u"mistyrose"_s,
642 u"moccasin"_s,
643 u"navajowhite"_s,
644 u"navy"_s,
645 u"oldlace"_s,
646 u"olive"_s,
647 u"olivedrab"_s,
648 u"orange"_s,
649 u"orangered"_s,
650 u"orchid"_s,
651 u"palegoldenrod"_s,
652 u"palegreen"_s,
653 u"paleturquoise"_s,
654 u"palevioletred"_s,
655 u"papayawhip"_s,
656 u"peachpuff"_s,
657 u"peru"_s,
658 u"pink"_s,
659 u"plum"_s,
660 u"powderblue"_s,
661 u"purple"_s,
662 u"red"_s,
663 u"rosybrown"_s,
664 u"royalblue"_s,
665 u"saddlebrown"_s,
666 u"salmon"_s,
667 u"sandybrown"_s,
668 u"seagreen"_s,
669 u"seashell"_s,
670 u"sienna"_s,
671 u"silver"_s,
672 u"skyblue"_s,
673 u"slateblue"_s,
674 u"slategray"_s,
675 u"slategrey"_s,
676 u"snow"_s,
677 u"springgreen"_s,
678 u"steelblue"_s,
679 u"tan"_s,
680 u"teal"_s,
681 u"thistle"_s,
682 u"tomato"_s,
683 u"turquoise"_s,
684 u"violet"_s,
685 u"wheat"_s,
686 u"white"_s,
687 u"whitesmoke"_s,
688 u"yellow"_s,
689 u"yellowgreen"_s,
690 };
691};
692
693
694ColorValidatorPass::ColorValidatorPass(QQmlSA::PassManager *manager)
695 : PropertyPass(manager), m_colorType(resolveType("QtQuick"_L1, "color"_L1))
696{
697 Q_ASSERT_X(std::is_sorted(m_colorNames.cbegin(), m_colorNames.cend()), "ColorValidatorPass",
698 "m_colorNames should be sorted!");
699}
700
701void ColorValidatorPass::onBinding(const QQmlSA::Element &element, const QString &propertyName,
702 const QQmlSA::Binding &binding, const QQmlSA::Element &,
703 const QQmlSA::Element &)
704{
705 if (binding.bindingType() != QQmlSA::BindingType::StringLiteral)
706 return;
707 const auto propertyType = element.property(propertyName).type();
708 if (!propertyType || propertyType != m_colorType)
709 return;
710
711 QString colorName = binding.stringValue();
712 // for "named" colors, QColor::fromString does not care about
713 // the case
714 if (!colorName.startsWith(u'#'))
715 colorName = std::move(colorName).toLower();
716 if (s_hexPattern.match(colorName).hasMatch())
717 return;
718
719 if (std::binary_search(m_colorNames.cbegin(), m_colorNames.cend(), colorName))
720 return;
721
722 if (colorName == u"transparent")
723 return;
724
725 auto suggestion = QQmlJSUtils::didYouMean(
726 colorName, m_colorNames, element.filePath(),
727 QQmlSA::SourceLocationPrivate::sourceLocation(binding.sourceLocation()));
728
729 emitWarningWithOptionalFix(*this, "Invalid color \"%1\"."_L1.arg(colorName), quickColor,
730 binding.sourceLocation(), suggestion);
731}
732
733void AttachedPropertyReuse::onRead(const QQmlSA::Element &element, const QString &propertyName,
734 const QQmlSA::Element &readScope,
735 QQmlSA::SourceLocation location)
736{
737 const auto range = usedAttachedTypes.equal_range(readScope);
738 const auto attachedTypeAndLocation = std::find_if(
739 range.first, range.second, [&](const ElementAndLocation &elementAndLocation) {
740 return elementAndLocation.element == element;
741 });
742 if (attachedTypeAndLocation != range.second) {
743 const QQmlSA::SourceLocation attachedLocation = attachedTypeAndLocation->location;
744
745 // Ignore enum accesses, as these will not cause the attached object to be created.
746 // Also ignore anything we cannot determine.
747 if (!element.hasProperty(propertyName) && !element.hasMethod(propertyName))
748 return;
749
750 for (QQmlSA::Element scope = readScope.parentScope(); !scope.isNull();
751 scope = scope.parentScope()) {
752 const auto range = usedAttachedTypes.equal_range(scope);
753 bool found = false;
754 for (auto it = range.first; it != range.second; ++it) {
755 if (it->element == element) {
756 found = true;
757 break;
758 }
759 }
760 if (!found)
761 continue;
762
763 const QString id = resolveElementToId(scope, readScope);
764 const QQmlSA::SourceLocation idInsertLocation{ attachedLocation.offset(), 0,
765 attachedLocation.startLine(),
766 attachedLocation.startColumn() };
767 QString m = "Reference it by id instead%1:"_L1;
768 m = m.arg(id.isEmpty() ? " (You first have to give the element and id)"_L1 : ""_L1);
769 QQmlSA::FixSuggestion suggestion{
770 m, idInsertLocation, { readScope.filePath(), idInsertLocation,
771 id.isEmpty() ? u"<id>."_s : (id + '.'_L1) }
772 };
773
774 if (!id.isEmpty())
775 suggestion.setAutoApplicable();
776
777 emitWarning("Using attached type %1 already initialized in a parent scope."_L1.arg(
778 element.name()),
779 category, attachedLocation, suggestion);
780 return;
781 }
782
783 return;
784 }
785
786 if (element.hasProperty(propertyName))
787 return; // an actual property
788
789 QQmlSA::Element type = resolveTypeInFileScope(propertyName);
790 QQmlSA::Element attached = resolveAttachedInFileScope(propertyName);
791 if (!type || !attached)
792 return;
793
794 if (category == quickControlsAttachedPropertyReuse) {
795 for (QQmlSA::Element parent = attached; parent; parent = parent.baseType()) {
796 // ### TODO: Make it possible to resolve QQuickAttachedPropertyPropagator
797 // so that we don't have to compare the internal id
798 if (parent.internalId() == "QQuickAttachedPropertyPropagator"_L1) {
799 usedAttachedTypes.insert(readScope, {attached, location});
800 break;
801 }
802 }
803
804 } else {
805 usedAttachedTypes.insert(readScope, {attached, location});
806 }
807}
808
809void AttachedPropertyReuse::onWrite(const QQmlSA::Element &element, const QString &propertyName,
810 const QQmlSA::Element &value, const QQmlSA::Element &writeScope,
811 QQmlSA::SourceLocation location)
812{
813 Q_UNUSED(value);
814 onRead(element, propertyName, writeScope, location);
815}
816
817void QmlLintQuickPlugin::registerPasses(QQmlSA::PassManager *manager,
818 const QQmlSA::Element &rootElement)
819{
820 const QQmlSA::LoggerWarningId attachedReuseCategory = [manager]() {
821 if (manager->isCategoryEnabled(quickAttachedPropertyReuse))
822 return quickAttachedPropertyReuse;
823 if (manager->isCategoryEnabled(qmlAttachedPropertyReuse))
824 return qmlAttachedPropertyReuse;
825 return quickControlsAttachedPropertyReuse;
826 }();
827
828 const bool hasQuick = manager->hasImportedModule("QtQuick");
829 const bool hasQuickLayouts = manager->hasImportedModule("QtQuick.Layouts");
830 const bool hasQuickControls = manager->hasImportedModule("QtQuick.Templates")
831 || manager->hasImportedModule("QtQuick.Controls")
832 || manager->hasImportedModule("QtQuick.Controls.Basic");
833
834 Q_UNUSED(rootElement);
835
836 if (hasQuick) {
837 manager->registerElementPass(std::make_unique<AnchorsValidatorPass>(manager));
838 manager->registerElementPass(std::make_unique<PropertyChangesValidatorPass>(manager));
839 manager->registerElementPass(std::make_unique<StateNoItemChildrenValidator>(manager));
840 manager->registerPropertyPass(std::make_unique<QQuickLiteralBindingCheck>(manager),
841 QAnyStringView(), QAnyStringView());
842 manager->registerPropertyPass(std::make_unique<ColorValidatorPass>(manager),
843 QAnyStringView(), QAnyStringView());
844
845 auto forbiddenChildProperty =
846 std::make_unique<ForbiddenChildrenPropertyValidatorPass>(manager);
847
848 for (const QString &element : { u"Grid"_s, u"Flow"_s }) {
849 for (const QString &property : { u"anchors"_s, u"x"_s, u"y"_s }) {
850 forbiddenChildProperty->addWarning(
851 "QtQuick", element, property,
852 u"Cannot specify %1 for items inside %2. %2 will not function."_s.arg(
853 property, element));
854 }
855 }
856
857 if (hasQuickLayouts) {
858 forbiddenChildProperty->addWarning(
859 "QtQuick.Layouts", "Layout", "anchors",
860 "Detected anchors on an item that is managed by a layout. This is undefined "
861 u"behavior; use Layout.alignment instead.");
862 forbiddenChildProperty->addWarning(
863 "QtQuick.Layouts", "Layout", "x",
864 "Detected x on an item that is managed by a layout. This is undefined "
865 u"behavior; use Layout.leftMargin or Layout.rightMargin instead.");
866 forbiddenChildProperty->addWarning(
867 "QtQuick.Layouts", "Layout", "y",
868 "Detected y on an item that is managed by a layout. This is undefined "
869 u"behavior; use Layout.topMargin or Layout.bottomMargin instead.");
870 forbiddenChildProperty->addWarning(
871 "QtQuick.Layouts", "Layout", "width",
872 "Detected width on an item that is managed by a layout. This is undefined "
873 u"behavior; use implicitWidth or Layout.preferredWidth instead.");
874 forbiddenChildProperty->addWarning(
875 "QtQuick.Layouts", "Layout", "height",
876 "Detected height on an item that is managed by a layout. This is undefined "
877 u"behavior; use implictHeight or Layout.preferredHeight instead.");
878 }
879
880 manager->registerElementPass(std::move(forbiddenChildProperty));
881 }
882
883 auto attachedPropertyType = std::make_shared<AttachedPropertyTypeValidatorPass>(manager);
884
885 auto addAttachedWarning = [&](TypeDescription attachedType, QList<TypeDescription> allowedTypes,
886 QAnyStringView warning, bool allowInDelegate = false) {
887 QString attachedTypeName = attachedPropertyType->addWarning(attachedType, allowedTypes,
888 allowInDelegate, warning);
889 if (attachedTypeName.isEmpty())
890 return;
891
892 manager->registerPropertyPass(attachedPropertyType, attachedType.module,
893 u"$internal$."_s + attachedTypeName, {}, false);
894 };
895
896 auto addVarBindingWarning =
897 [&](QAnyStringView moduleName, QAnyStringView typeName,
898 const QMultiHash<QString, TypeDescription> &expectedPropertyTypes) {
899 auto varBindingType = std::make_shared<VarBindingTypeValidatorPass>(
900 manager, expectedPropertyTypes);
901 for (const auto &propertyName : expectedPropertyTypes.uniqueKeys()) {
902 manager->registerPropertyPass(varBindingType, moduleName, typeName,
903 propertyName);
904 }
905 };
906
907 if (hasQuick) {
908 addVarBindingWarning("QtQuick", "TableView",
909 { { "columnWidthProvider", { "", "function" } },
910 { "rowHeightProvider", { "", "function" } } });
911 addAttachedWarning({ "QtQuick", "Accessible" },
912 { { "QtQuick", "Item" }, { "QtQuick.Templates", "Action" } },
913 "Accessible attached property must be attached to an object deriving "
914 "from Item or Action");
915 addAttachedWarning({ "QtQuick", "LayoutMirroring" },
916 { { "QtQuick", "Item" }, { "QtQuick", "Window" } },
917 "LayoutMirroring attached property must be attached to an object deriving from Item or Window");
918 addAttachedWarning({ "QtQuick", "EnterKey" }, { { "QtQuick", "Item" } },
919 "EnterKey attached property must be attached to an object deriving from Item");
920 }
921 if (hasQuickLayouts) {
922 addAttachedWarning({ "QtQuick.Layouts", "Layout" }, { { "QtQuick", "Item" } },
923 "Layout attached property must be attached to an object deriving from Item");
924 addAttachedWarning({ "QtQuick.Layouts", "StackLayout" }, { { "QtQuick", "Item" } },
925 "StackLayout attached property must be attached to an object deriving from Item");
926 }
927
928
929 if (hasQuickControls) {
930 manager->registerElementPass(std::make_unique<ControlsSwipeDelegateValidatorPass>(manager));
931 manager->registerPropertyPass(std::make_unique<AttachedPropertyReuse>(
932 manager, attachedReuseCategory), "", "");
933
934 addAttachedWarning({ "QtQuick.Templates", "ScrollBar" },
935 { { "QtQuick", "Flickable" }, { "QtQuick.Templates", "ScrollView" } },
936 "ScrollBar attached property must be attached to an object deriving from Flickable or ScrollView");
937 addAttachedWarning({ "QtQuick.Templates", "ScrollIndicator" },
938 { { "QtQuick", "Flickable" } },
939 "ScrollIndicator attached property must be attached to an object deriving from Flickable");
940 addAttachedWarning({ "QtQuick.Templates", "TextArea" }, { { "QtQuick", "Flickable" } },
941 "TextArea attached property must be attached to an object deriving from Flickable");
942 addAttachedWarning({ "QtQuick.Templates", "SplitView" }, { { "QtQuick", "Item" } },
943 "SplitView attached property must be attached to an object deriving from Item");
944 addAttachedWarning({ "QtQuick.Templates", "StackView" }, { { "QtQuick", "Item" } },
945 "StackView attached property must be attached to an object deriving from Item");
946 addAttachedWarning({ "QtQuick.Templates", "ToolTip" }, { { "QtQuick", "Item" } },
947 "ToolTip attached property must be attached to an object deriving from Item");
948 addAttachedWarning({ "QtQuick.Templates", "SwipeDelegate" }, { { "QtQuick", "Item" } },
949 "SwipeDelegate attached property must be attached to an object deriving from Item");
950 addAttachedWarning({ "QtQuick.Templates", "SwipeView" }, { { "QtQuick", "Item" } },
951 "SwipeView attached property must be attached to an object deriving from Item");
952 addVarBindingWarning("QtQuick.Templates", "Tumbler",
953 { { "contentItem", { "QtQuick", "PathView" } },
954 { "contentItem", { "QtQuick", "ListView" } } });
955 addVarBindingWarning("QtQuick.Templates", "SpinBox",
956 { { "textFromValue", { "", "function" } },
957 { "valueFromText", { "", "function" } } });
958 } else if (attachedReuseCategory != quickControlsAttachedPropertyReuse) {
959 manager->registerPropertyPass(std::make_unique<AttachedPropertyReuse>(
960 manager, attachedReuseCategory), "", "");
961 }
962
963 if (manager->hasImportedModule(u"QtQuick.Controls.macOS"_s)
964 || manager->hasImportedModule(u"QtQuick.Controls.Windows"_s))
965 manager->registerElementPass(std::make_unique<ControlsNativeValidatorPass>(manager));
966}
967
970 , m_propertyChanges(resolveType("QtQuick", "PropertyChanges"))
971{
972}
973
974bool PropertyChangesValidatorPass::shouldRun(const QQmlSA::Element &element)
975{
976 return element.inherits(m_propertyChanges);
977}
978
979void PropertyChangesValidatorPass::run(const QQmlSA::Element &element)
980{
981 const QQmlSA::Binding::Bindings bindings = element.ownPropertyBindings();
982
983 const auto target =
984 std::find_if(bindings.constBegin(), bindings.constEnd(),
985 [](const auto binding) { return binding.propertyName() == u"target"_s; });
986 if (target == bindings.constEnd())
987 return;
988
989 QString targetId = u"<id>"_s;
990 const auto targetLocation = target.value().sourceLocation();
991 const QString targetBinding = sourceCode(targetLocation);
992 const QQmlSA::Element targetElement = resolveIdToElement(targetBinding, element);
993 if (!targetElement.isNull())
994 targetId = targetBinding;
995
996 bool hadCustomParsedBindings = false;
997 for (auto it = bindings.constBegin(); it != bindings.constEnd(); ++it) {
998 const auto &propertyName = it.key();
999 const auto &propertyBinding = it.value();
1000 if (element.hasProperty(propertyName))
1001 continue;
1002
1003 const QQmlSA::SourceLocation bindingLocation = propertyBinding.sourceLocation();
1004 if (!targetElement.isNull() && !targetElement.hasProperty(propertyName)) {
1005 emitWarning(
1006 "Unknown property \"%1\" in PropertyChanges."_L1.arg(propertyName),
1007 quickPropertyChangesParsed, bindingLocation);
1008 continue;
1009 }
1010
1011 QString binding = sourceCode(bindingLocation);
1012 if (binding.length() > 16)
1013 binding = binding.left(13) + "..."_L1;
1014
1015 hadCustomParsedBindings = true;
1016 emitWarning("Property \"%1\" is custom-parsed in PropertyChanges. "
1017 "You should phrase this binding as \"%2.%1: %3\""_L1.arg(propertyName, targetId,
1018 binding),
1019 quickPropertyChangesParsed, bindingLocation);
1020 }
1021
1022 if (hadCustomParsedBindings && !targetElement.isNull()) {
1023 emitWarning("You should remove any bindings on the \"target\" property and avoid "
1024 "custom-parsed bindings in PropertyChanges.",
1025 quickPropertyChangesParsed, targetLocation);
1026 }
1027}
1028
1031 , m_state(resolveType("QtQuick", "State"))
1032 , m_anchorChanges(resolveType("QtQuick", "AnchorChanges"))
1033 , m_parentChanges(resolveType("QtQuick", "ParentChange"))
1034 , m_propertyChanges(resolveType("QtQuick", "PropertyChanges"))
1035 , m_stateChangeScript(resolveType("QtQuick", "StateChangeScript"))
1036{}
1037
1038bool StateNoItemChildrenValidator::shouldRun(const QQmlSA::Element &element)
1039{
1040 return element.inherits(m_state);
1041}
1042
1043void StateNoItemChildrenValidator::run(const QQmlSA::Element &element)
1044{
1045 const auto &childScopes = QQmlJSScope::scope(element)->childScopes();
1046 for (const auto &child : childScopes) {
1047 if (child->scopeType() != QQmlSA::ScopeType::QMLScope)
1048 continue;
1049
1050 if (child->inherits(QQmlJSScope::scope(m_anchorChanges))
1051 || child->inherits(QQmlJSScope::scope(m_parentChanges))
1052 || child->inherits(QQmlJSScope::scope(m_propertyChanges))
1053 || child->inherits(QQmlJSScope::scope(m_stateChangeScript))) {
1054 continue;
1055 }
1056 QString msg = "A State cannot have a child item of type %1"_L1.arg(child->baseTypeName());
1057 auto loc = QQmlSA::SourceLocationPrivate::createQQmlSASourceLocation(
1058 child->sourceLocation());
1059 emitWarning(msg, quickStateNoChildItem, loc);
1060 }
1061}
1062
1063QT_END_NAMESPACE
1064
1065#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