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

Provided by KDAB

Privacy Policy
Start learning QML with our Intro Training
Find out more

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