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

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