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
6QT_BEGIN_NAMESPACE
7
8using namespace Qt::StringLiterals;
9
10static constexpr QQmlSA::LoggerWarningId quickLayoutPositioning { "Quick.layout-positioning" };
11static constexpr QQmlSA::LoggerWarningId quickAttachedPropertyType { "Quick.attached-property-type" };
12static constexpr QQmlSA::LoggerWarningId quickControlsNativeCustomize { "Quick.controls-native-customize" };
13static constexpr QQmlSA::LoggerWarningId quickAnchorCombinations { "Quick.anchor-combinations" };
14static constexpr QQmlSA::LoggerWarningId quickUnexpectedVarType { "Quick.unexpected-var-type" };
15static constexpr QQmlSA::LoggerWarningId quickPropertyChangesParsed { "Quick.property-changes-parsed" };
16static constexpr QQmlSA::LoggerWarningId quickControlsAttachedPropertyReuse { "Quick.controls-attached-property-reuse" };
17static constexpr QQmlSA::LoggerWarningId quickAttachedPropertyReuse { "Quick.attached-property-reuse" };
18
19ForbiddenChildrenPropertyValidatorPass::ForbiddenChildrenPropertyValidatorPass(
20 QQmlSA::PassManager *manager)
21 : QQmlSA::ElementPass(manager)
22{
23}
24
25void ForbiddenChildrenPropertyValidatorPass::addWarning(QAnyStringView moduleName,
26 QAnyStringView typeName,
27 QAnyStringView propertyName,
28 QAnyStringView warning)
29{
30 auto element = resolveType(moduleName, typeName);
31 if (!element.isNull())
32 m_types[element].append(t: { .propertyName: propertyName.toString(), .message: warning.toString() });
33}
34
35bool ForbiddenChildrenPropertyValidatorPass::shouldRun(const QQmlSA::Element &element)
36{
37 if (!element.parentScope())
38 return false;
39
40 for (const auto pair : m_types.asKeyValueRange()) {
41 if (element.parentScope().inherits(pair.first))
42 return true;
43 }
44
45 return false;
46}
47
48void ForbiddenChildrenPropertyValidatorPass::run(const QQmlSA::Element &element)
49{
50 for (const auto elementPair : m_types.asKeyValueRange()) {
51 const QQmlSA::Element &type = elementPair.first;
52 if (!element.parentScope().inherits(type))
53 continue;
54
55 for (const auto &warning : elementPair.second) {
56 if (!element.hasOwnPropertyBindings(propertyName: warning.propertyName))
57 continue;
58
59 const auto bindings = element.ownPropertyBindings(propertyName: warning.propertyName);
60 const auto firstBinding = bindings.constBegin().value();
61 emitWarning(diagnostic: warning.message, id: quickLayoutPositioning, srcLocation: firstBinding.sourceLocation());
62 }
63 break;
64 }
65}
66
67AttachedPropertyTypeValidatorPass::AttachedPropertyTypeValidatorPass(QQmlSA::PassManager *manager)
68 : QQmlSA::PropertyPass(manager)
69{
70}
71
72QString AttachedPropertyTypeValidatorPass::addWarning(TypeDescription attachType,
73 QList<TypeDescription> allowedTypes,
74 bool allowInDelegate, QAnyStringView warning)
75{
76 QVarLengthArray<QQmlSA::Element, 4> elements;
77
78 const QQmlSA::Element baseType = resolveType(moduleName: attachType.module, typeName: attachType.name);
79 const QQmlSA::Element attachedType = resolveAttached(moduleName: attachType.module, typeName: attachType.name);
80
81 for (const TypeDescription &desc : allowedTypes) {
82 const QQmlSA::Element type = resolveType(moduleName: desc.module, typeName: desc.name);
83 if (type.isNull())
84 continue;
85 elements.push_back(t: type);
86 }
87
88 m_attachedTypes.insert(
89 hash: { std::make_pair<>(x: attachedType.internalId(),
90 y: Warning{ .allowedTypes: elements, .allowInDelegate: allowInDelegate, .message: warning.toString() }) });
91
92 return attachedType.internalId();
93}
94
95void AttachedPropertyTypeValidatorPass::checkWarnings(const QQmlSA::Element &element,
96 const QQmlSA::Element &scopeUsedIn,
97 const QQmlSA::SourceLocation &location)
98{
99 auto warning = m_attachedTypes.constFind(key: element.internalId());
100 if (warning == m_attachedTypes.cend())
101 return;
102 for (const QQmlSA::Element &type : warning->allowedTypes) {
103 if (scopeUsedIn.inherits(type))
104 return;
105 }
106
107 if (warning->allowInDelegate) {
108 if (scopeUsedIn.isPropertyRequired(propertyName: u"index"_s)
109 || scopeUsedIn.isPropertyRequired(propertyName: u"model"_s))
110 return;
111
112 // If the scope is at the root level, we cannot know whether it will be used
113 // as a delegate or not.
114 // ### TODO: add a method to check whether a scope is the global scope
115 // so that we do not need to use internalId
116 if (!scopeUsedIn.parentScope() || scopeUsedIn.parentScope().internalId() == u"global"_s)
117 return;
118
119 for (const QQmlSA::Binding &binding :
120 scopeUsedIn.parentScope().propertyBindings(propertyName: u"delegate"_s)) {
121 if (!binding.hasObject())
122 continue;
123 if (binding.objectType() == scopeUsedIn)
124 return;
125 }
126 }
127
128 emitWarning(diagnostic: warning->message, id: quickAttachedPropertyType, srcLocation: location);
129}
130
131void AttachedPropertyTypeValidatorPass::onBinding(const QQmlSA::Element &element,
132 const QString &propertyName,
133 const QQmlSA::Binding &binding,
134 const QQmlSA::Element &bindingScope,
135 const QQmlSA::Element &value)
136{
137 Q_UNUSED(element)
138 Q_UNUSED(propertyName)
139 Q_UNUSED(bindingScope)
140 Q_UNUSED(value)
141
142 checkWarnings(element: bindingScope.baseType(), scopeUsedIn: element, location: binding.sourceLocation());
143}
144
145void AttachedPropertyTypeValidatorPass::onRead(const QQmlSA::Element &element,
146 const QString &propertyName,
147 const QQmlSA::Element &readScope,
148 QQmlSA::SourceLocation location)
149{
150 // If the attachment does not have such a property or method then
151 // it's either a more general error or an enum. Enums are fine.
152 if (element.hasProperty(propertyName) || element.hasMethod(methodName: propertyName))
153 checkWarnings(element, scopeUsedIn: readScope, location);
154}
155
156void AttachedPropertyTypeValidatorPass::onWrite(const QQmlSA::Element &element,
157 const QString &propertyName,
158 const QQmlSA::Element &value,
159 const QQmlSA::Element &writeScope,
160 QQmlSA::SourceLocation location)
161{
162 Q_UNUSED(propertyName)
163 Q_UNUSED(value)
164
165 checkWarnings(element, scopeUsedIn: writeScope, location);
166}
167
168ControlsNativeValidatorPass::ControlsNativeValidatorPass(QQmlSA::PassManager *manager)
169 : QQmlSA::ElementPass(manager)
170{
171 m_elements = {
172 ControlElement { .name: "Control",
173 .restrictedProperties: QStringList { "background", "contentItem", "leftPadding", "rightPadding",
174 "topPadding", "bottomPadding", "horizontalPadding",
175 "verticalPadding", "padding" },
176 .isInModuleControls: false, .isControl: true },
177 ControlElement { .name: "Button", .restrictedProperties: QStringList { "indicator" } },
178 ControlElement {
179 .name: "ApplicationWindow",
180 .restrictedProperties: QStringList { "background", "contentItem", "header", "footer", "menuBar" } },
181 ControlElement { .name: "ComboBox", .restrictedProperties: QStringList { "indicator" } },
182 ControlElement { .name: "Dial", .restrictedProperties: QStringList { "handle" } },
183 ControlElement { .name: "GroupBox", .restrictedProperties: QStringList { "label" } },
184 ControlElement { .name: "$internal$.QQuickIndicatorButton", .restrictedProperties: QStringList { "indicator" }, .isInModuleControls: false },
185 ControlElement { .name: "Label", .restrictedProperties: QStringList { "background" } },
186 ControlElement { .name: "MenuItem", .restrictedProperties: QStringList { "arrow" } },
187 ControlElement { .name: "Page", .restrictedProperties: QStringList { "header", "footer" } },
188 ControlElement { .name: "Popup", .restrictedProperties: QStringList { "background", "contentItem" } },
189 ControlElement { .name: "RangeSlider", .restrictedProperties: QStringList { "handle" } },
190 ControlElement { .name: "Slider", .restrictedProperties: QStringList { "handle" } },
191 ControlElement { .name: "$internal$.QQuickSwipe",
192 .restrictedProperties: QStringList { "leftItem", "behindItem", "rightItem" }, .isInModuleControls: false },
193 ControlElement { .name: "TextArea", .restrictedProperties: QStringList { "background" } },
194 ControlElement { .name: "TextField", .restrictedProperties: QStringList { "background" } },
195 };
196
197 for (const QString &module : { u"QtQuick.Controls.macOS"_s, u"QtQuick.Controls.Windows"_s }) {
198 if (!manager->hasImportedModule(name: module))
199 continue;
200
201 QQmlSA::Element control = resolveType(moduleName: module, typeName: "Control");
202
203 for (ControlElement &element : m_elements) {
204 auto type = resolveType(moduleName: element.isInModuleControls ? module : "QtQuick.Templates",
205 typeName: element.name);
206
207 if (type.isNull())
208 continue;
209
210 element.inheritsControl = !element.isControl && type.inherits(control);
211 element.element = type;
212 }
213
214 m_elements.removeIf(pred: [](const ControlElement &element) { return element.element.isNull(); });
215
216 break;
217 }
218}
219
220bool ControlsNativeValidatorPass::shouldRun(const QQmlSA::Element &element)
221{
222 for (const ControlElement &controlElement : m_elements) {
223 // If our element inherits control, we don't have to individually check for them here.
224 if (controlElement.inheritsControl)
225 continue;
226 if (element.inherits(controlElement.element))
227 return true;
228 }
229 return false;
230}
231
232void ControlsNativeValidatorPass::run(const QQmlSA::Element &element)
233{
234 for (const ControlElement &controlElement : m_elements) {
235 if (element.inherits(controlElement.element)) {
236 for (const QString &propertyName : controlElement.restrictedProperties) {
237 if (element.hasOwnPropertyBindings(propertyName)) {
238 emitWarning(QStringLiteral("Not allowed to override \"%1\" because native "
239 "styles cannot be customized: See "
240 "https://doc-snapshots.qt.io/qt6-dev/"
241 "qtquickcontrols-customize.html#customization-"
242 "reference for more information.")
243 .arg(a: propertyName),
244 id: quickControlsNativeCustomize, srcLocation: element.sourceLocation());
245 }
246 }
247 // Since all the different types we have rules for don't inherit from each other (except
248 // for Control) we don't have to keep checking whether other types match once we've
249 // found one that has been inherited from.
250 if (!controlElement.isControl)
251 break;
252 }
253 }
254}
255
256AnchorsValidatorPass::AnchorsValidatorPass(QQmlSA::PassManager *manager)
257 : QQmlSA::ElementPass(manager)
258 , m_item(resolveType(moduleName: "QtQuick", typeName: "Item"))
259{
260}
261
262bool AnchorsValidatorPass::shouldRun(const QQmlSA::Element &element)
263{
264 return !m_item.isNull() && element.inherits(m_item)
265 && element.hasOwnPropertyBindings(propertyName: u"anchors"_s);
266}
267
268void AnchorsValidatorPass::run(const QQmlSA::Element &element)
269{
270 enum BindingLocation { Exists = 1, Own = (1 << 1) };
271 QHash<QString, qint8> bindings;
272
273 const QStringList properties = { u"left"_s, u"right"_s, u"horizontalCenter"_s,
274 u"top"_s, u"bottom"_s, u"verticalCenter"_s,
275 u"baseline"_s };
276
277 QList<QQmlSA::Binding> anchorBindings = element.propertyBindings(propertyName: u"anchors"_s);
278
279 for (qsizetype i = anchorBindings.size() - 1; i >= 0; i--) {
280 auto groupType = anchorBindings[i].groupType();
281 if (groupType.isNull())
282 continue;
283
284 for (const QString &name : properties) {
285
286 const auto &propertyBindings = groupType.ownPropertyBindings(propertyName: name);
287 if (propertyBindings.begin() == propertyBindings.end())
288 continue;
289
290 bool isUndefined = false;
291 for (const auto &propertyBinding : propertyBindings) {
292 if (propertyBinding.hasUndefinedScriptValue()) {
293 isUndefined = true;
294 break;
295 }
296 }
297
298 if (isUndefined)
299 bindings[name] = 0;
300 else
301 bindings[name] |= Exists | ((i == 0) ? Own : 0);
302 }
303 }
304
305 auto ownSourceLocation = [&](QStringList properties) -> QQmlSA::SourceLocation {
306 QQmlSA::SourceLocation warnLoc;
307
308 for (const QString &name : properties) {
309 if (bindings[name] & Own) {
310 QQmlSA::Element groupType = QQmlSA::Element{ anchorBindings[0].groupType() };
311 auto bindings = groupType.ownPropertyBindings(propertyName: name);
312 Q_ASSERT(bindings.begin() != bindings.end());
313 warnLoc = bindings.begin().value().sourceLocation();
314 break;
315 }
316 }
317 return warnLoc;
318 };
319
320 if ((bindings[u"left"_s] & bindings[u"right"_s] & bindings[u"horizontalCenter"_s]) & Exists) {
321 QQmlSA::SourceLocation warnLoc =
322 ownSourceLocation({ u"left"_s, u"right"_s, u"horizontalCenter"_s });
323
324 if (warnLoc.isValid()) {
325 emitWarning(
326 diagnostic: "Cannot specify left, right, and horizontalCenter anchors at the same time.",
327 id: quickAnchorCombinations, srcLocation: warnLoc);
328 }
329 }
330
331 if ((bindings[u"top"_s] & bindings[u"bottom"_s] & bindings[u"verticalCenter"_s]) & Exists) {
332 QQmlSA::SourceLocation warnLoc =
333 ownSourceLocation({ u"top"_s, u"bottom"_s, u"verticalCenter"_s });
334 if (warnLoc.isValid()) {
335 emitWarning(diagnostic: "Cannot specify top, bottom, and verticalCenter anchors at the same time.",
336 id: quickAnchorCombinations, srcLocation: warnLoc);
337 }
338 }
339
340 if ((bindings[u"baseline"_s] & (bindings[u"bottom"_s] | bindings[u"verticalCenter"_s]))
341 & Exists) {
342 QQmlSA::SourceLocation warnLoc =
343 ownSourceLocation({ u"baseline"_s, u"bottom"_s, u"verticalCenter"_s });
344 if (warnLoc.isValid()) {
345 emitWarning(diagnostic: "Baseline anchor cannot be used in conjunction with top, bottom, or "
346 "verticalCenter anchors.",
347 id: quickAnchorCombinations, srcLocation: warnLoc);
348 }
349 }
350}
351
352ControlsSwipeDelegateValidatorPass::ControlsSwipeDelegateValidatorPass(QQmlSA::PassManager *manager)
353 : QQmlSA::ElementPass(manager)
354 , m_swipeDelegate(resolveType(moduleName: "QtQuick.Controls", typeName: "SwipeDelegate"))
355{
356}
357
358bool ControlsSwipeDelegateValidatorPass::shouldRun(const QQmlSA::Element &element)
359{
360 return !m_swipeDelegate.isNull() && element.inherits(m_swipeDelegate);
361}
362
363void ControlsSwipeDelegateValidatorPass::run(const QQmlSA::Element &element)
364{
365 for (const auto &property : { u"background"_s, u"contentItem"_s }) {
366 for (const auto &binding : element.ownPropertyBindings(propertyName: property)) {
367 if (!binding.hasObject())
368 continue;
369 const QQmlSA::Element element = QQmlSA::Element{ binding.objectType() };
370 const auto &bindings = element.propertyBindings(propertyName: u"anchors"_s);
371 if (bindings.isEmpty())
372 continue;
373
374 if (bindings.first().bindingType() != QQmlSA::BindingType::GroupProperty)
375 continue;
376
377 auto anchors = bindings.first().groupType();
378 for (const auto &disallowed : { u"fill"_s, u"centerIn"_s, u"left"_s, u"right"_s }) {
379 if (anchors.hasPropertyBindings(name: disallowed)) {
380 QQmlSA::SourceLocation location;
381 const auto &ownBindings = anchors.ownPropertyBindings(propertyName: disallowed);
382 if (ownBindings.begin() != ownBindings.end()) {
383 location = ownBindings.begin().value().sourceLocation();
384 }
385
386 emitWarning(
387 diagnostic: u"SwipeDelegate: Cannot use horizontal anchors with %1; unable to layout the item."_s
388 .arg(a: property),
389 id: quickAnchorCombinations, srcLocation: location);
390 break;
391 }
392 }
393 break;
394 }
395 }
396
397 const auto &swipe = element.ownPropertyBindings(propertyName: u"swipe"_s);
398 if (swipe.begin() == swipe.end())
399 return;
400
401 const auto firstSwipe = swipe.begin().value();
402 if (firstSwipe.bindingType() != QQmlSA::BindingType::GroupProperty)
403 return;
404
405 auto group = firstSwipe.groupType();
406
407 const std::array ownDirBindings = { group.ownPropertyBindings(propertyName: u"right"_s),
408 group.ownPropertyBindings(propertyName: u"left"_s),
409 group.ownPropertyBindings(propertyName: u"behind"_s) };
410
411 auto ownBindingIterator =
412 std::find_if(first: ownDirBindings.begin(), last: ownDirBindings.end(),
413 pred: [](const auto &bindings) { return bindings.begin() != bindings.end(); });
414
415 if (ownBindingIterator == ownDirBindings.end())
416 return;
417
418 if (group.hasPropertyBindings(name: u"behind"_s)
419 && (group.hasPropertyBindings(name: u"right"_s) || group.hasPropertyBindings(name: u"left"_s))) {
420 emitWarning(diagnostic: "SwipeDelegate: Cannot set both behind and left/right properties",
421 id: quickAnchorCombinations, srcLocation: ownBindingIterator->begin().value().sourceLocation());
422 }
423}
424
425VarBindingTypeValidatorPass::VarBindingTypeValidatorPass(
426 QQmlSA::PassManager *manager,
427 const QMultiHash<QString, TypeDescription> &expectedPropertyTypes)
428 : QQmlSA::PropertyPass(manager)
429{
430 QMultiHash<QString, QQmlSA::Element> propertyTypes;
431
432 for (const auto pair : expectedPropertyTypes.asKeyValueRange()) {
433 const QQmlSA::Element propType = pair.second.module.isEmpty()
434 ? resolveBuiltinType(typeName: pair.second.name)
435 : resolveType(moduleName: pair.second.module, typeName: pair.second.name);
436 if (!propType.isNull())
437 propertyTypes.insert(key: pair.first, value: propType);
438 }
439
440 m_expectedPropertyTypes = propertyTypes;
441}
442
443void VarBindingTypeValidatorPass::onBinding(const QQmlSA::Element &element,
444 const QString &propertyName,
445 const QQmlSA::Binding &binding,
446 const QQmlSA::Element &bindingScope,
447 const QQmlSA::Element &value)
448{
449 Q_UNUSED(element);
450 Q_UNUSED(bindingScope);
451
452 const auto range = m_expectedPropertyTypes.equal_range(key: propertyName);
453
454 if (range.first == range.second)
455 return;
456
457 QQmlSA::Element bindingType;
458
459 if (!value.isNull()) {
460 bindingType = value;
461 } else {
462 if (QQmlSA::Binding::isLiteralBinding(binding.bindingType())) {
463 bindingType = resolveLiteralType(binding);
464 } else {
465 switch (binding.bindingType()) {
466 case QQmlSA::BindingType::Object:
467 bindingType = QQmlSA::Element{ binding.objectType() };
468 break;
469 case QQmlSA::BindingType::Script:
470 break;
471 default:
472 return;
473 }
474 }
475 }
476
477 if (std::find_if(first: range.first, last: range.second,
478 pred: [&](const QQmlSA::Element &scope) { return bindingType.inherits(scope); })
479 == range.second) {
480
481 const bool bindingTypeIsComposite = bindingType.isComposite();
482 if (bindingTypeIsComposite && !bindingType.baseType()) {
483 /* broken module or missing import, there is nothing we
484 can really check here, as something is amiss. We
485 simply skip this binding, and assume that whatever
486 caused the breakage here will already cause another
487 warning somewhere else.
488 */
489 return;
490 }
491 const QString bindingTypeName =
492 bindingTypeIsComposite ? bindingType.baseType().name()
493 : bindingType.name();
494 QStringList expectedTypeNames;
495
496 for (auto it = range.first; it != range.second; it++)
497 expectedTypeNames << it.value().name();
498
499 emitWarning(diagnostic: u"Unexpected type for property \"%1\" expected %2 got %3"_s.arg(
500 args: propertyName, args: expectedTypeNames.join(sep: u", "_s), args: bindingTypeName),
501 id: quickUnexpectedVarType, srcLocation: binding.sourceLocation());
502 }
503}
504
505void AttachedPropertyReuse::onRead(const QQmlSA::Element &element, const QString &propertyName,
506 const QQmlSA::Element &readScope,
507 QQmlSA::SourceLocation location)
508{
509 const auto range = usedAttachedTypes.equal_range(key: readScope);
510 const auto attachedTypeAndLocation = std::find_if(
511 first: range.first, last: range.second, pred: [&](const ElementAndLocation &elementAndLocation) {
512 return elementAndLocation.element == element;
513 });
514 if (attachedTypeAndLocation != range.second) {
515 const QQmlSA::SourceLocation attachedLocation = attachedTypeAndLocation->location;
516
517 // Ignore enum accesses, as these will not cause the attached object to be created.
518 // Also ignore anything we cannot determine.
519 if (!element.hasProperty(propertyName) && !element.hasMethod(methodName: propertyName))
520 return;
521
522 for (QQmlSA::Element scope = readScope.parentScope(); !scope.isNull();
523 scope = scope.parentScope()) {
524 const auto range = usedAttachedTypes.equal_range(key: scope);
525 bool found = false;
526 for (auto it = range.first; it != range.second; ++it) {
527 if (it->element == element) {
528 found = true;
529 break;
530 }
531 }
532 if (!found)
533 continue;
534
535 const QString id = resolveElementToId(element: scope, context: readScope);
536 const QQmlSA::SourceLocation idInsertLocation{ attachedLocation.offset(), 0,
537 attachedLocation.startLine(),
538 attachedLocation.startColumn() };
539 QQmlSA::FixSuggestion suggestion{ "Reference it by id instead:"_L1, idInsertLocation,
540 id.isEmpty() ? "<id>."_L1 : (id + '.'_L1) };
541
542 if (id.isEmpty())
543 suggestion.setHint("You first have to give the element an id"_L1);
544 else
545 suggestion.setAutoApplicable();
546
547 emitWarning(diagnostic: "Using attached type %1 already initialized in a parent scope."_L1.arg(
548 args: element.name()),
549 id: category, srcLocation: attachedLocation, fix: suggestion);
550 }
551
552 return;
553 }
554
555 if (element.hasProperty(propertyName))
556 return; // an actual property
557
558 QQmlSA::Element type = resolveTypeInFileScope(typeName: propertyName);
559 QQmlSA::Element attached = resolveAttachedInFileScope(typeName: propertyName);
560 if (!type || !attached)
561 return;
562
563 if (category == quickControlsAttachedPropertyReuse) {
564 for (QQmlSA::Element parent = attached; parent; parent = parent.baseType()) {
565 // ### TODO: Make it possible to resolve QQuickAttachedPropertyPropagator
566 // so that we don't have to compare the internal id
567 if (parent.internalId() == "QQuickAttachedPropertyPropagator"_L1) {
568 usedAttachedTypes.insert(key: readScope, value: {.element: attached, .location: location});
569 break;
570 }
571 }
572
573 } else {
574 usedAttachedTypes.insert(key: readScope, value: {.element: attached, .location: location});
575 }
576}
577
578void AttachedPropertyReuse::onWrite(const QQmlSA::Element &element, const QString &propertyName,
579 const QQmlSA::Element &value, const QQmlSA::Element &writeScope,
580 QQmlSA::SourceLocation location)
581{
582 Q_UNUSED(value);
583 onRead(element, propertyName, readScope: writeScope, location);
584}
585
586void QmlLintQuickPlugin::registerPasses(QQmlSA::PassManager *manager,
587 const QQmlSA::Element &rootElement)
588{
589 const QQmlSA::LoggerWarningId attachedReuseCategory = [manager]() {
590 if (manager->isCategoryEnabled(category: quickAttachedPropertyReuse))
591 return quickAttachedPropertyReuse;
592 if (manager->isCategoryEnabled(category: qmlAttachedPropertyReuse))
593 return qmlAttachedPropertyReuse;
594 return quickControlsAttachedPropertyReuse;
595 }();
596
597 const bool hasQuick = manager->hasImportedModule(name: "QtQuick");
598 const bool hasQuickLayouts = manager->hasImportedModule(name: "QtQuick.Layouts");
599 const bool hasQuickControls = manager->hasImportedModule(name: "QtQuick.Templates")
600 || manager->hasImportedModule(name: "QtQuick.Controls")
601 || manager->hasImportedModule(name: "QtQuick.Controls.Basic");
602
603 Q_UNUSED(rootElement);
604
605 if (hasQuick) {
606 manager->registerElementPass(pass: std::make_unique<AnchorsValidatorPass>(args&: manager));
607 manager->registerElementPass(pass: std::make_unique<PropertyChangesValidatorPass>(args&: manager));
608
609 auto forbiddenChildProperty =
610 std::make_unique<ForbiddenChildrenPropertyValidatorPass>(args&: manager);
611
612 for (const QString &element : { u"Grid"_s, u"Flow"_s }) {
613 for (const QString &property : { u"anchors"_s, u"x"_s, u"y"_s }) {
614 forbiddenChildProperty->addWarning(
615 moduleName: "QtQuick", typeName: element, propertyName: property,
616 warning: u"Cannot specify %1 for items inside %2. %2 will not function."_s.arg(
617 args: property, args: element));
618 }
619 }
620
621 if (hasQuickLayouts) {
622 forbiddenChildProperty->addWarning(
623 moduleName: "QtQuick.Layouts", typeName: "Layout", propertyName: "anchors",
624 warning: "Detected anchors on an item that is managed by a layout. This is undefined "
625 u"behavior; use Layout.alignment instead.");
626 forbiddenChildProperty->addWarning(
627 moduleName: "QtQuick.Layouts", typeName: "Layout", propertyName: "x",
628 warning: "Detected x on an item that is managed by a layout. This is undefined "
629 u"behavior; use Layout.leftMargin or Layout.rightMargin instead.");
630 forbiddenChildProperty->addWarning(
631 moduleName: "QtQuick.Layouts", typeName: "Layout", propertyName: "y",
632 warning: "Detected y on an item that is managed by a layout. This is undefined "
633 u"behavior; use Layout.topMargin or Layout.bottomMargin instead.");
634 forbiddenChildProperty->addWarning(
635 moduleName: "QtQuick.Layouts", typeName: "Layout", propertyName: "width",
636 warning: "Detected width on an item that is managed by a layout. This is undefined "
637 u"behavior; use implicitWidth or Layout.preferredWidth instead.");
638 forbiddenChildProperty->addWarning(
639 moduleName: "QtQuick.Layouts", typeName: "Layout", propertyName: "height",
640 warning: "Detected height on an item that is managed by a layout. This is undefined "
641 u"behavior; use implictHeight or Layout.preferredHeight instead.");
642 }
643
644 manager->registerElementPass(pass: std::move(forbiddenChildProperty));
645 }
646
647 auto attachedPropertyType = std::make_shared<AttachedPropertyTypeValidatorPass>(args&: manager);
648
649 auto addAttachedWarning = [&](TypeDescription attachedType, QList<TypeDescription> allowedTypes,
650 QAnyStringView warning, bool allowInDelegate = false) {
651 QString attachedTypeName = attachedPropertyType->addWarning(attachType: attachedType, allowedTypes,
652 allowInDelegate, warning);
653 manager->registerPropertyPass(pass: attachedPropertyType, moduleName: attachedType.module,
654 typeName: u"$internal$."_s + attachedTypeName, propertyName: {}, allowInheritance: false);
655 };
656
657 auto addVarBindingWarning =
658 [&](QAnyStringView moduleName, QAnyStringView typeName,
659 const QMultiHash<QString, TypeDescription> &expectedPropertyTypes) {
660 auto varBindingType = std::make_shared<VarBindingTypeValidatorPass>(
661 args&: manager, args: expectedPropertyTypes);
662 for (const auto &propertyName : expectedPropertyTypes.uniqueKeys()) {
663 manager->registerPropertyPass(pass: varBindingType, moduleName, typeName,
664 propertyName);
665 }
666 };
667
668 if (hasQuick) {
669 addVarBindingWarning("QtQuick", "TableView",
670 { { "columnWidthProvider", { .module: "", .name: "function" } },
671 { "rowHeightProvider", { .module: "", .name: "function" } } });
672 addAttachedWarning({ .module: "QtQuick", .name: "Accessible" }, { { .module: "QtQuick", .name: "Item" } },
673 "Accessible must be attached to an Item");
674 addAttachedWarning({ .module: "QtQuick", .name: "LayoutMirroring" },
675 { { .module: "QtQuick", .name: "Item" }, { .module: "QtQuick", .name: "Window" } },
676 "LayoutDirection attached property only works with Items and Windows");
677 addAttachedWarning({ .module: "QtQuick", .name: "EnterKey" }, { { .module: "QtQuick", .name: "Item" } },
678 "EnterKey attached property only works with Items");
679 }
680 if (hasQuickLayouts) {
681 addAttachedWarning({ .module: "QtQuick.Layouts", .name: "Layout" }, { { .module: "QtQuick", .name: "Item" } },
682 "Layout must be attached to Item elements");
683 addAttachedWarning({ .module: "QtQuick.Layouts", .name: "StackLayout" }, { { .module: "QtQuick", .name: "Item" } },
684 "StackLayout must be attached to an Item");
685 }
686
687
688 if (hasQuickControls) {
689 manager->registerElementPass(pass: std::make_unique<ControlsSwipeDelegateValidatorPass>(args&: manager));
690 manager->registerPropertyPass(pass: std::make_unique<AttachedPropertyReuse>(
691 args&: manager, args: attachedReuseCategory), moduleName: "", typeName: "");
692
693 addAttachedWarning({ .module: "QtQuick.Templates", .name: "ScrollBar" },
694 { { .module: "QtQuick", .name: "Flickable" }, { .module: "QtQuick.Templates", .name: "ScrollView" } },
695 "ScrollBar must be attached to a Flickable or ScrollView");
696 addAttachedWarning({ .module: "QtQuick.Templates", .name: "ScrollIndicator" },
697 { { .module: "QtQuick", .name: "Flickable" } },
698 "ScrollIndicator must be attached to a Flickable");
699 addAttachedWarning({ .module: "QtQuick.Templates", .name: "TextArea" }, { { .module: "QtQuick", .name: "Flickable" } },
700 "TextArea must be attached to a Flickable");
701 addAttachedWarning({ .module: "QtQuick.Templates", .name: "SplitView" }, { { .module: "QtQuick", .name: "Item" } },
702 "SplitView attached property only works with Items");
703 addAttachedWarning({ .module: "QtQuick.Templates", .name: "StackView" }, { { .module: "QtQuick", .name: "Item" } },
704 "StackView attached property only works with Items");
705 addAttachedWarning({ .module: "QtQuick.Templates", .name: "ToolTip" }, { { .module: "QtQuick", .name: "Item" } },
706 "ToolTip must be attached to an Item");
707 addAttachedWarning({ .module: "QtQuick.Templates", .name: "SwipeDelegate" }, { { .module: "QtQuick", .name: "Item" } },
708 "Attached properties of SwipeDelegate must be accessed through an Item");
709 addAttachedWarning({ .module: "QtQuick.Templates", .name: "SwipeView" }, { { .module: "QtQuick", .name: "Item" } },
710 "SwipeView must be attached to an Item");
711 addAttachedWarning(
712 { .module: "QtQuick.Templates", .name: "Tumbler" }, { { .module: "QtQuick", .name: "Tumbler" } },
713 "Tumbler: attached properties of Tumbler must be accessed through a delegate item",
714 true);
715 addVarBindingWarning("QtQuick.Templates", "Tumbler",
716 { { "contentItem", { .module: "QtQuick", .name: "PathView" } },
717 { "contentItem", { .module: "QtQuick", .name: "ListView" } } });
718 addVarBindingWarning("QtQuick.Templates", "SpinBox",
719 { { "textFromValue", { .module: "", .name: "function" } },
720 { "valueFromText", { .module: "", .name: "function" } } });
721 } else if (attachedReuseCategory != quickControlsAttachedPropertyReuse) {
722 manager->registerPropertyPass(pass: std::make_unique<AttachedPropertyReuse>(
723 args&: manager, args: attachedReuseCategory), moduleName: "", typeName: "");
724 }
725
726 if (manager->hasImportedModule(name: u"QtQuick.Controls.macOS"_s)
727 || manager->hasImportedModule(name: u"QtQuick.Controls.Windows"_s))
728 manager->registerElementPass(pass: std::make_unique<ControlsNativeValidatorPass>(args&: manager));
729}
730
731PropertyChangesValidatorPass::PropertyChangesValidatorPass(QQmlSA::PassManager *manager)
732 : QQmlSA::ElementPass(manager)
733 , m_propertyChanges(resolveType(moduleName: "QtQuick", typeName: "PropertyChanges"))
734{
735}
736
737bool PropertyChangesValidatorPass::shouldRun(const QQmlSA::Element &element)
738{
739 return !m_propertyChanges.isNull() && element.inherits(m_propertyChanges);
740}
741
742void PropertyChangesValidatorPass::run(const QQmlSA::Element &element)
743{
744 const QQmlSA::Binding::Bindings bindings = element.ownPropertyBindings();
745
746 const auto target =
747 std::find_if(first: bindings.constBegin(), last: bindings.constEnd(),
748 pred: [](const auto binding) { return binding.propertyName() == u"target"_s; });
749 if (target == bindings.constEnd())
750 return;
751
752 QString targetId = u"<id>"_s;
753 const QString targetBinding = sourceCode(location: target.value().sourceLocation());
754 const QQmlSA::Element targetElement = resolveIdToElement(id: targetBinding, context: element);
755 if (!targetElement.isNull())
756 targetId = targetBinding;
757
758 for (auto it = bindings.constBegin(); it != bindings.constEnd(); ++it) {
759 const auto &propertyName = it.key();
760 const auto &propertyBinding = it.value();
761 if (element.hasProperty(propertyName))
762 continue;
763
764 const QQmlSA::SourceLocation bindingLocation = propertyBinding.sourceLocation();
765 if (!targetElement.isNull() && !targetElement.hasProperty(propertyName)) {
766 emitWarning(
767 diagnostic: "Unknown property \"%1\" in PropertyChanges."_L1.arg(args: propertyName),
768 id: quickPropertyChangesParsed, srcLocation: bindingLocation);
769 continue;
770 }
771
772 QString binding = sourceCode(location: bindingLocation);
773 if (binding.length() > 16)
774 binding = binding.left(n: 13) + "..."_L1;
775
776 emitWarning(diagnostic: "Property \"%1\" is custom-parsed in PropertyChanges. "
777 "You should phrase this binding as \"%2.%1: %3\""_L1.arg(args: propertyName, args&: targetId,
778 args&: binding),
779 id: quickPropertyChangesParsed, srcLocation: bindingLocation);
780 }
781}
782
783QT_END_NAMESPACE
784
785#include "moc_quicklintplugin.cpp"
786

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