| 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 |  | 
| 6 | QT_BEGIN_NAMESPACE | 
| 7 |  | 
| 8 | using namespace Qt::StringLiterals; | 
| 9 |  | 
| 10 | static constexpr QQmlSA::LoggerWarningId quickLayoutPositioning { "Quick.layout-positioning"  }; | 
| 11 | static constexpr QQmlSA::LoggerWarningId quickAttachedPropertyType { "Quick.attached-property-type"  }; | 
| 12 | static constexpr QQmlSA::LoggerWarningId quickControlsNativeCustomize { "Quick.controls-native-customize"  }; | 
| 13 | static constexpr QQmlSA::LoggerWarningId quickAnchorCombinations { "Quick.anchor-combinations"  }; | 
| 14 | static constexpr QQmlSA::LoggerWarningId quickUnexpectedVarType { "Quick.unexpected-var-type"  }; | 
| 15 | static constexpr QQmlSA::LoggerWarningId quickPropertyChangesParsed { "Quick.property-changes-parsed"  }; | 
| 16 | static constexpr QQmlSA::LoggerWarningId quickControlsAttachedPropertyReuse { "Quick.controls-attached-property-reuse"  }; | 
| 17 | static constexpr QQmlSA::LoggerWarningId quickAttachedPropertyReuse { "Quick.attached-property-reuse"  }; | 
| 18 |  | 
| 19 | ForbiddenChildrenPropertyValidatorPass::ForbiddenChildrenPropertyValidatorPass( | 
| 20 |         QQmlSA::PassManager *manager) | 
| 21 |     : QQmlSA::ElementPass(manager) | 
| 22 | { | 
| 23 | } | 
| 24 |  | 
| 25 | void 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 |  | 
| 35 | bool 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 |  | 
| 48 | void 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 |  | 
| 76 | AttachedPropertyTypeValidatorPass::AttachedPropertyTypeValidatorPass(QQmlSA::PassManager *manager) | 
| 77 |     : QQmlSA::PropertyPass(manager) | 
| 78 | { | 
| 79 | } | 
| 80 |  | 
| 81 | QString 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 |  | 
| 109 | void 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 |  | 
| 145 | void 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 |  | 
| 164 | void 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 |  | 
| 175 | void 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 |  | 
| 187 | ControlsNativeValidatorPass::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 |  | 
| 239 | bool 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 |  | 
| 251 | void 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 |  | 
| 275 | AnchorsValidatorPass::AnchorsValidatorPass(QQmlSA::PassManager *manager) | 
| 276 |     : QQmlSA::ElementPass(manager) | 
| 277 |     , m_item(resolveType(moduleName: "QtQuick" , typeName: "Item" )) | 
| 278 | { | 
| 279 | } | 
| 280 |  | 
| 281 | bool 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 |  | 
| 287 | void 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 |  | 
| 371 | ControlsSwipeDelegateValidatorPass::ControlsSwipeDelegateValidatorPass(QQmlSA::PassManager *manager) | 
| 372 |     : QQmlSA::ElementPass(manager) | 
| 373 |     , m_swipeDelegate(resolveType(moduleName: "QtQuick.Controls" , typeName: "SwipeDelegate" )) | 
| 374 | { | 
| 375 | } | 
| 376 |  | 
| 377 | bool ControlsSwipeDelegateValidatorPass::shouldRun(const QQmlSA::Element &element) | 
| 378 | { | 
| 379 |     return !m_swipeDelegate.isNull() && element.inherits(m_swipeDelegate); | 
| 380 | } | 
| 381 |  | 
| 382 | void 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 |  | 
| 444 | VarBindingTypeValidatorPass::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 |  | 
| 462 | void 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 |  | 
| 524 | void 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 |  | 
| 598 | void 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 |  | 
| 606 | void 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 |  | 
| 750 | PropertyChangesValidatorPass::PropertyChangesValidatorPass(QQmlSA::PassManager *manager) | 
| 751 |     : QQmlSA::ElementPass(manager) | 
| 752 |     , m_propertyChanges(resolveType(moduleName: "QtQuick" , typeName: "PropertyChanges" )) | 
| 753 | { | 
| 754 | } | 
| 755 |  | 
| 756 | bool PropertyChangesValidatorPass::shouldRun(const QQmlSA::Element &element) | 
| 757 | { | 
| 758 |     return !m_propertyChanges.isNull() && element.inherits(m_propertyChanges); | 
| 759 | } | 
| 760 |  | 
| 761 | void 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 |  | 
| 811 | QT_END_NAMESPACE | 
| 812 |  | 
| 813 | #include "moc_quicklintplugin.cpp" | 
| 814 |  |